Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Selection insert fix #102

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions src/Database/IStructure.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ function getColumns($table);
function getPrimaryKey($table);

/**
* Returns table primary key sequence.
* Returns table autoincrement primary key and sequence his name, if exists.
* @param string
* @return string|NULL
* @return array|NULL
*/
function getPrimaryKeySequence($table);

Expand Down
19 changes: 11 additions & 8 deletions src/Database/Structure.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,21 @@ public function getPrimaryKeySequence($table)
$this->needStructure();
$table = $this->resolveFQTableName($table);

if (!$this->connection->getSupplementalDriver()->isSupported(ISupplementalDriver::SUPPORT_SEQUENCE)) {
return NULL;
}

$primary = $this->getPrimaryKey($table);
if (!$primary || is_array($primary)) {
if (!$primary) {
return NULL;
}

foreach ($this->structure['columns'][$table] as $columnMeta) {
if ($columnMeta['name'] === $primary) {
return isset($columnMeta['vendor']['sequence']) ? $columnMeta['vendor']['sequence'] : NULL;
$isSupported = $this->connection->getSupplementalDriver()->isSupported(ISupplementalDriver::SUPPORT_SEQUENCE);

foreach ((array) $primary as $key) {
foreach ($this->structure['columns'][$table] as $columnMeta) {
if ($columnMeta['name'] === $key && $columnMeta['autoincrement']) {
return [
'name' => $key,
'sequence' => ($isSupported && isset($columnMeta['vendor']['sequence'])) ? $columnMeta['vendor']['sequence'] : NULL
];
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Database/Table/GroupedSelection.php
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ protected function emptyResultSet($saveCache = TRUE, $deleteRererencedCache = TR
/********************* manipulation ****************d*g**/


public function insert($data)
public function insert($data, $returnRow = TRUE)
{
if ($data instanceof \Traversable && !$data instanceof Selection) {
$data = iterator_to_array($data);
Expand Down
53 changes: 26 additions & 27 deletions src/Database/Table/Selection.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Selection implements \Iterator, IRowContainer, \ArrayAccess, \Countable
/** @var string|array|NULL primary key field name */
protected $primary;

/** @var string|bool primary column sequence name, FALSE for autodetection */
/** @var array|bool|NULL primary column sequence name, FALSE for autodetection */
protected $primarySequence = FALSE;

/** @var IRow[] data read from database in [primary key => IRow] format */
Expand Down Expand Up @@ -136,7 +136,7 @@ public function getPrimary($need = TRUE)


/**
* @return string
* @return array|NULL
*/
public function getPrimarySequence()
{
Expand All @@ -149,10 +149,10 @@ public function getPrimarySequence()


/**
* @param string
* @param array
* @return self
*/
public function setPrimarySequence($sequence)
public function setPrimarySequence(array $sequence)
{
$this->primarySequence = $sequence;
return $this;
Expand Down Expand Up @@ -801,9 +801,10 @@ public function getDataRefreshed()
/**
* Inserts row in a table.
* @param array|\Traversable|Selection array($column => $value)|\Traversable|Selection for INSERT ... SELECT
* @return IRow|int|bool Returns IRow or number of affected rows for Selection or table without primary key
* @param bool
* @return IRow|int|bool Returns IRow or number of affected rows for Selection, multi insert or table without primary key
*/
public function insert($data)
public function insert($data, $returnRow = TRUE)
{
if ($data instanceof self) {
$return = $this->context->queryArgs($this->sqlBuilder->buildInsertQuery() . ' ' . $data->getSql(), $data->getSqlBuilder()->getParameters());
Expand All @@ -817,36 +818,34 @@ public function insert($data)

$this->loadRefCache();

if ($data instanceof self || $this->primary === NULL) {
unset($this->refCache['referencing'][$this->getGeneralCacheKey()][$this->getSpecificCacheKey()]);
return $return->getRowCount();
}

$primaryKey = $this->context->getInsertId(
($tmp = $this->getPrimarySequence())
? implode('.', array_map([$this->context->getConnection()->getSupplementalDriver(), 'delimite'], explode('.', $tmp)))
: NULL
);
if ($primaryKey === FALSE) {
if ($data instanceof self || $this->primary === NULL || Nette\Utils\Arrays::isList($data) || !$returnRow) {
unset($this->refCache['referencing'][$this->getGeneralCacheKey()][$this->getSpecificCacheKey()]);
return $return->getRowCount();
}

if (is_array($this->getPrimary())) {
$primaryKey = [];

foreach ((array) $this->getPrimary() as $key) {
if (!isset($data[$key])) {
return $data;
}

$primaryKey = [];
foreach ((array) $this->getPrimary() as $key) {
if (isset($data[$key])) {
$primaryKey[$key] = $data[$key];
}
if (count($primaryKey) === 1) {
$primaryKey = reset($primaryKey);
}

if ($sequenceColumn = $this->getPrimarySequence()) {
$primaryKey[$sequenceColumn['name']] = $this->context->getInsertId(
($sequenceColumn['sequence'])
? implode('.', array_map([$this->context->getConnection()->getSupplementalDriver(), 'delimite'], explode('.', $sequenceColumn['sequence'])))
: NULL
);
if ($primaryKey[$sequenceColumn['name']] === FALSE) {
unset($this->refCache['referencing'][$this->getGeneralCacheKey()][$this->getSpecificCacheKey()]);
return $return->getRowCount();
}
}

if (count($primaryKey) === 1) {
$primaryKey = reset($primaryKey);
}

$row = $this->createSelectionInstance()
->select('*')
->wherePrimary($primaryKey)
Expand Down
30 changes: 15 additions & 15 deletions tests/Database/Structure.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -58,24 +58,24 @@ class StructureTestCase extends TestCase
['name' => 'books_view', 'view' => TRUE],
]);
$this->driver->shouldReceive('getColumns')->with('authors')->once()->andReturn([
['name' => 'id', 'primary' => TRUE, 'vendor' => ['sequence' => '"public"."authors_id_seq"']],
['name' => 'name', 'primary' => FALSE, 'vendor' => []],
['name' => 'id', 'primary' => TRUE, 'autoincrement' => TRUE, 'vendor' => ['sequence' => '"public"."authors_id_seq"']],
['name' => 'name', 'primary' => FALSE, 'autoincrement' => FALSE, 'vendor' => []],
]);
$this->driver->shouldReceive('getColumns')->with('Books')->once()->andReturn([
['name' => 'id', 'primary' => TRUE, 'vendor' => ['sequence' => '"public"."Books_id_seq"']],
['name' => 'title', 'primary' => FALSE, 'vendor' => []],
['name' => 'id', 'primary' => TRUE, 'autoincrement' => TRUE, 'vendor' => ['sequence' => '"public"."Books_id_seq"']],
['name' => 'title', 'primary' => FALSE, 'autoincrement' => FALSE, 'vendor' => []],
]);
$this->driver->shouldReceive('getColumns')->with('tags')->once()->andReturn([
['name' => 'id', 'primary' => TRUE, 'vendor' => []],
['name' => 'name', 'primary' => FALSE, 'vendor' => []],
['name' => 'id', 'primary' => TRUE, 'autoincrement' => TRUE, 'vendor' => []],
['name' => 'name', 'primary' => FALSE, 'autoincrement' => FALSE, 'vendor' => []],
]);
$this->driver->shouldReceive('getColumns')->with('books_x_tags')->once()->andReturn([
['name' => 'book_id', 'primary' => TRUE, 'vendor' => []],
['name' => 'tag_id', 'primary' => TRUE, 'vendor' => []],
['name' => 'book_id', 'primary' => TRUE, 'autoincrement' => FALSE, 'vendor' => []],
['name' => 'tag_id', 'primary' => TRUE, 'autoincrement' => FALSE, 'vendor' => []],
]);
$this->driver->shouldReceive('getColumns')->with('books_view')->once()->andReturn([
['name' => 'id', 'primary' => FALSE, 'vendor' => []],
['name' => 'title', 'primary' => FALSE, 'vendor' => []],
['name' => 'id', 'primary' => FALSE, 'autoincrement' => FALSE, 'vendor' => []],
['name' => 'title', 'primary' => FALSE, 'autoincrement' => FALSE, 'vendor' => []],
]);
$this->connection->shouldReceive('getSupplementalDriver')->times(4)->andReturn($this->driver);
$this->driver->shouldReceive('getForeignKeys')->with('authors')->once()->andReturn([]);
Expand Down Expand Up @@ -108,8 +108,8 @@ class StructureTestCase extends TestCase
public function testGetColumns()
{
$columns = [
['name' => 'id', 'primary' => TRUE, 'vendor' => []],
['name' => 'name', 'primary' => FALSE, 'vendor' => []],
['name' => 'id', 'primary' => TRUE, 'autoincrement' => TRUE, 'vendor' => []],
['name' => 'name', 'primary' => FALSE, 'autoincrement' => FALSE, 'vendor' => []],
];

Assert::same($columns, $this->structure->getColumns('tags'));
Expand Down Expand Up @@ -138,9 +138,9 @@ class StructureTestCase extends TestCase
$this->driver->shouldReceive('isSupported')->with('sequence')->once()->andReturn(FALSE);
$this->driver->shouldReceive('isSupported')->with('sequence')->times(3)->andReturn(TRUE);

Assert::null($this->structure->getPrimaryKeySequence('Authors'));
Assert::same('"public"."authors_id_seq"', $this->structure->getPrimaryKeySequence('Authors'));
Assert::null($this->structure->getPrimaryKeySequence('tags'));
Assert::same(['name' => 'id', 'sequence' => NULL], $this->structure->getPrimaryKeySequence('Authors'));
Assert::same(['name' => 'id', 'sequence' => '"public"."authors_id_seq"'], $this->structure->getPrimaryKeySequence('Authors'));
Assert::same(['name' => 'id', 'sequence' => NULL] ,$this->structure->getPrimaryKeySequence('tags'));
Assert::null($this->structure->getPrimaryKeySequence('books_x_tags'));
}

Expand Down
6 changes: 4 additions & 2 deletions tests/Database/Table/Selection.insert().multi.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverN

test(function () use ($context) {
Assert::same(3, $context->table('author')->count());
$context->table('author')->insert([
$insert = $context->table('author')->insert([
[
'name' => 'Catelyn Stark',
'web' => 'http://example.com',
Expand All @@ -26,15 +26,17 @@ test(function () use ($context) {
'born' => new DateTime('2021-11-11'),
],
]); // INSERT INTO `author` (`name`, `web`, `born`) VALUES ('Catelyn Stark', 'http://example.com', '2011-11-11 00:00:00'), ('Sansa Stark', 'http://example.com', '2021-11-11 00:00:00')
Assert::same(2, $insert);
Assert::same(5, $context->table('author')->count());

$context->table('book_tag')->where('book_id', 1)->delete(); // DELETE FROM `book_tag` WHERE (`book_id` = ?)

Assert::same(4, $context->table('book_tag')->count());
$context->table('book')->get(1)->related('book_tag')->insert([ // SELECT * FROM `book` WHERE (`id` = ?)
$insert = $context->table('book')->get(1)->related('book_tag')->insert([ // SELECT * FROM `book` WHERE (`id` = ?)
['tag_id' => 21],
['tag_id' => 22],
['tag_id' => 23],
]); // INSERT INTO `book_tag` (`tag_id`, `book_id`) VALUES (21, 1), (22, 1), (23, 1)
Assert::same(3, $insert);
Assert::same(7, $context->table('book_tag')->count());
});
6 changes: 6 additions & 0 deletions tests/Database/Table/Selection.insert().phpt
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,9 @@ $inserted = $context->table('note')->insert([
'note' => 'Good one!',
]);
Assert::equal(1, $inserted);

$affected = $context->table('note')->insert([
'book_id' => 2,
'note' => 'Second one!',
], FALSE);
Assert::equal(1, $affected);
51 changes: 51 additions & 0 deletions tests/Database/Table/bugs/Selection.insert().phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
/**
* @dataProvider? ../../databases.ini
*/

use Tester\Assert;

require __DIR__ . '/../../connect.inc.php'; // create $connection

Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-nette_test4.sql");

//Insert into table without auto_increament primary key
test(function () use ($context) {

$inserted = $context->table('simple_pk')->insert([
'id' => 8,
'name' => 'Michal'
]);

Assert::equal(8, $inserted->id);
Assert::equal('Michal', $inserted->name);
});

//Insert into table with composite primary key
test(function () use ($context) {

$inserted = $context->table('composite_pk')->insert([
'id1' => 8,
'id2' => 10,
'name' => 'Michal'
]);

Assert::equal(8, $inserted->id1);
Assert::equal(10, $inserted->id2);
Assert::equal('Michal', $inserted->name);
});

//Insert into table with composite primary key and one of them is auto_increment
test(function () use ($context, $driverName) {

//Sqlite doesn't allow this type of table and sqlsrv's driver don't implement reflection
if ($driverName == 'mysql' || $driverName == 'pgsql') {
$inserted = $context->table('composite_pk_ai')->insert([
'id2' => 10,
'name' => 'Michal'
]);

Assert::equal(10, $inserted->id2);
Assert::equal('Michal', $inserted->name);
}
});
5 changes: 3 additions & 2 deletions tests/Database/Table/bugs/bug1342.postgre.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ $context->query('
');


$insertedRows = $context->table('bug1342')->insert([
$row = $context->table('bug1342')->insert([
'a1' => 1,
'a2' => 2,
]);

Assert::same(1, $insertedRows);
Assert::same(1, $row->a1);
Assert::same(2, $row->a2);
26 changes: 26 additions & 0 deletions tests/Database/files/mysql-nette_test4.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*!40102 SET storage_engine = InnoDB */;

DROP DATABASE IF EXISTS nette_test;
CREATE DATABASE nette_test;
USE nette_test;


CREATE TABLE simple_pk (
id int NOT NULL,
name varchar(100),
PRIMARY KEY (id)
);

CREATE TABLE composite_pk (
id1 int NOT NULL,
id2 int NOT NULL,
name varchar(100),
PRIMARY KEY (id1, id2)
);

CREATE TABLE composite_pk_ai (
id1 int NOT NULL AUTO_INCREMENT,
id2 int NOT NULL,
name varchar(100),
PRIMARY KEY (id1, id2)
);
22 changes: 22 additions & 0 deletions tests/Database/files/pgsql-nette_test4.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
DROP SCHEMA IF EXISTS public CASCADE;
CREATE SCHEMA public;

CREATE TABLE simple_pk (
id int NOT NULL,
name varchar(100),
PRIMARY KEY (id)
);

CREATE TABLE composite_pk (
id1 int NOT NULL,
id2 int NOT NULL,
name varchar(100),
PRIMARY KEY (id1, id2)
);

CREATE TABLE composite_pk_ai (
id1 serial NOT NULL,
id2 int NOT NULL,
name varchar(100),
PRIMARY KEY (id1, id2)
);
15 changes: 15 additions & 0 deletions tests/Database/files/sqlite-nette_test4.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
DROP TABLE IF EXISTS simple_pk;
DROP TABLE IF EXISTS composite_pk;

CREATE TABLE simple_pk (
id INT NOT NULL,
name TEXT,
PRIMARY KEY (id)
);

CREATE TABLE composite_pk (
id1 int NOT NULL,
id2 int NOT NULL,
name TEXT,
PRIMARY KEY (id1, id2)
);
Loading