diff --git a/src/Database/IStructure.php b/src/Database/IStructure.php index c92324497..6efcb339b 100644 --- a/src/Database/IStructure.php +++ b/src/Database/IStructure.php @@ -45,6 +45,13 @@ function getColumns($table); */ function getPrimaryKey($table); + /** + * Returns autoincrement primary key name. + * @param string + * @return string|NULL + */ + function getPrimaryAutoincrementKey($table); + /** * Returns table primary key sequence. * @param string diff --git a/src/Database/Structure.php b/src/Database/Structure.php index a80f95afc..71a2f2df3 100644 --- a/src/Database/Structure.php +++ b/src/Database/Structure.php @@ -65,6 +65,33 @@ public function getPrimaryKey($table) return $this->structure['primary'][$table]; } + public function getPrimaryAutoincrementKey($table) + { + $primaryKey = $this->getPrimaryKey($table); + if (!$primaryKey) { + return NULL; + } + + // Search for autoincrement key from multi primary key + if (is_array($primaryKey)) { + $keys = array_flip($primaryKey); + foreach ($this->getColumns($table) as $column) { + if (isset($keys[$column['name']]) && $column['autoincrement']) { + return $column['name']; + } + } + return NULL; + } + + // Search for autoincrement key from simple primary key + foreach ($this->getColumns($table) as $column) { + if ($column['name'] == $primaryKey) { + return $column['autoincrement'] ? $column['name'] : NULL; + } + } + + return NULL; + } public function getPrimaryKeySequence($table) { @@ -75,13 +102,14 @@ public function getPrimaryKeySequence($table) return NULL; } - $primary = $this->getPrimaryKey($table); - if (!$primary || is_array($primary)) { + $autoincrementPrimaryKeyName = $this->getPrimaryAutoincrementKey($table); + if (!$autoincrementPrimaryKeyName) { return NULL; } + // Search for sequence from simple primary key foreach ($this->structure['columns'][$table] as $columnMeta) { - if ($columnMeta['name'] === $primary) { + if ($columnMeta['name'] === $autoincrementPrimaryKeyName) { return isset($columnMeta['vendor']['sequence']) ? $columnMeta['vendor']['sequence'] : NULL; } } diff --git a/src/Database/Table/Selection.php b/src/Database/Table/Selection.php index df35aa47f..27ba5ac41 100644 --- a/src/Database/Table/Selection.php +++ b/src/Database/Table/Selection.php @@ -828,29 +828,40 @@ public function insert($data) return $return->getRowCount(); } - $primaryKey = $this->context->getInsertId( - ($tmp = $this->getPrimarySequence()) - ? implode('.', array_map([$this->context->getConnection()->getSupplementalDriver(), 'delimite'], explode('.', $tmp))) - : NULL - ); - if (!$primaryKey) { - unset($this->refCache['referencing'][$this->getGeneralCacheKey()][$this->getSpecificCacheKey()]); - return $return->getRowCount(); + $primarySequenceName = $this->getPrimarySequence(); + $primaryAutoincrementKey = $this->context->getStructure()->getPrimaryAutoincrementKey($this->name); + + $primaryKey = []; + foreach ((array) $this->primary as $key) { + if (isset($data[$key])) { + $primaryKey[$key] = $data[$key]; + } } - if (is_array($this->getPrimary())) { - $primaryKey = []; + // First check sequence + if (!empty($primarySequenceName) && $primaryAutoincrementKey) { + $primaryKey[$primaryAutoincrementKey] = $this->context->getInsertId($this->context->getConnection()->getSupplementalDriver()->delimite($primarySequenceName)); + + // Autoincrement primary without sequence + } elseif ($primaryAutoincrementKey) { + $primaryKey[$primaryAutoincrementKey] = $this->context->getInsertId($primarySequenceName); - foreach ((array) $this->getPrimary() as $key) { + // Multi column primary without autoincrement + } elseif (is_array($this->primary)) { + foreach ($this->primary as $key) { if (!isset($data[$key])) { return $data; } - - $primaryKey[$key] = $data[$key]; - } - if (count($primaryKey) === 1) { - $primaryKey = reset($primaryKey); } + + // Primary without autoincrement, try get primary from inserting data + } elseif ($this->primary && isset($data[$this->primary])) { + $primaryKey = $data[$this->primary]; + + // If primaryKey cannot be prepared, return inserted rows count + } else { + unset($this->refCache['referencing'][$this->getGeneralCacheKey()][$this->getSpecificCacheKey()]); + return $return->getRowCount(); } $row = $this->createSelectionInstance() diff --git a/tests/Database/Structure.phpt b/tests/Database/Structure.phpt index c1298da7b..9cd2849cc 100644 --- a/tests/Database/Structure.phpt +++ b/tests/Database/Structure.phpt @@ -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' => FALSE, '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([]); @@ -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' => FALSE, 'vendor' => []], + ['name' => 'name', 'primary' => FALSE,'autoincrement' => FALSE, 'vendor' => []], ]; Assert::same($columns, $this->structure->getColumns('tags')); diff --git a/tests/Database/Table/Selection.insert().primaryKeys.phpt b/tests/Database/Table/Selection.insert().primaryKeys.phpt new file mode 100644 index 000000000..c927d8ab1 --- /dev/null +++ b/tests/Database/Table/Selection.insert().primaryKeys.phpt @@ -0,0 +1,110 @@ +table('simple_pk_autoincrement')->insert([ + 'note' => 'Some note here' + ]); + + Assert::type(\Nette\Database\Table\ActiveRow::class, $simplePkAutoincrementResult); + Assert::equal(1, $simplePkAutoincrementResult->identifier1); + Assert::equal('Some note here', $simplePkAutoincrementResult->note); + + $simplePkAutoincrementResult2 = $context->table('simple_pk_autoincrement')->insert([ + 'note' => 'Some note here 2' + ]); + + Assert::type(\Nette\Database\Table\ActiveRow::class, $simplePkAutoincrementResult2); + Assert::equal(2, $simplePkAutoincrementResult2->identifier1); + Assert::equal('Some note here 2', $simplePkAutoincrementResult2->note); +}); + +// Insert into table with simple primary index (no autoincrement) +test(function() use ($context) { + $simplePkNoAutoincrementResult = $context->table('simple_pk_no_autoincrement')->insert([ + 'identifier1' => 100, + 'note' => 'Some note here' + ]); + + Assert::type(\Nette\Database\Table\ActiveRow::class, $simplePkNoAutoincrementResult); + Assert::equal(100, $simplePkNoAutoincrementResult->identifier1); + Assert::equal('Some note here', $simplePkNoAutoincrementResult->note); + + $simplePkNoAutoincrementResult2 = $context->table('simple_pk_no_autoincrement')->insert([ + 'identifier1' => 200, + 'note' => 'Some note here 2' + ]); + + Assert::type(\Nette\Database\Table\ActiveRow::class, $simplePkNoAutoincrementResult2); + Assert::equal(200, $simplePkNoAutoincrementResult2->identifier1); + Assert::equal('Some note here 2', $simplePkNoAutoincrementResult2->note); +}); + +// Insert into table with multi column primary index (no autoincrement) +test(function() use ($context) { + $multiPkNoAutoincrementResult = $context->table('multi_pk_no_autoincrement')->insert([ + 'identifier1' => 5, + 'identifier2' => 10, + 'note' => 'Some note here' + ]); + + Assert::type(\Nette\Database\Table\ActiveRow::class, $multiPkNoAutoincrementResult); + Assert::equal(5, $multiPkNoAutoincrementResult->identifier1); + Assert::equal(10, $multiPkNoAutoincrementResult->identifier2); + Assert::equal('Some note here', $multiPkNoAutoincrementResult->note); + + $multiPkNoAutoincrementResult2 = $context->table('multi_pk_no_autoincrement')->insert([ + 'identifier1' => 5, + 'identifier2' => 100, + 'note' => 'Some note here 2' + ]); + + Assert::type(\Nette\Database\Table\ActiveRow::class, $multiPkNoAutoincrementResult2); + Assert::equal(5, $multiPkNoAutoincrementResult2->identifier1); + Assert::equal(100, $multiPkNoAutoincrementResult2->identifier2); + Assert::equal('Some note here 2', $multiPkNoAutoincrementResult2->note); +}); + +// Insert into table with multi column primary index (autoincrement) +test(function() use ($driverName, $context) { + if (in_array($driverName, ['mysql', 'pgsql'])) { + $multiPkAutoincrementResult = $context->table('multi_pk_autoincrement')->insert([ + 'identifier2' => 999, + 'note' => 'Some note here' + ]); + + Assert::type(\Nette\Database\Table\ActiveRow::class, $multiPkAutoincrementResult); + Assert::equal(1, $multiPkAutoincrementResult->identifier1); + Assert::equal(999, $multiPkAutoincrementResult->identifier2); + Assert::equal('Some note here', $multiPkAutoincrementResult->note); + + $multiPkAutoincrementResult2 = $context->table('multi_pk_autoincrement')->insert([ + 'identifier2' => 999, + 'note' => 'Some note here 2' + ]); + + Assert::type(\Nette\Database\Table\ActiveRow::class, $multiPkAutoincrementResult2); + Assert::equal(2, $multiPkAutoincrementResult2->identifier1); + Assert::equal(999, $multiPkAutoincrementResult2->identifier2); + Assert::equal('Some note here 2', $multiPkAutoincrementResult2->note); + } +}); + +// Insert into table without primary key +test(function() use ($context) { + $noPkResult1 = $context->table('no_pk')->insert([ + 'note' => 'Some note here', + ]); + Assert::equal(1, $noPkResult1); +}); diff --git a/tests/Database/Table/bugs/bug1342.postgre.phpt b/tests/Database/Table/bugs/bug1342.postgre.phpt index dcf7a5a21..2707f3305 100644 --- a/tests/Database/Table/bugs/bug1342.postgre.phpt +++ b/tests/Database/Table/bugs/bug1342.postgre.phpt @@ -25,4 +25,13 @@ $insertedRows = $context->table('bug1342')->insert([ 'a2' => 2, ]); -Assert::same(1, $insertedRows); +Assert::same($insertedRows->a1, 1); +Assert::same($insertedRows->a2, 2); + +$insertedRows = $context->table('bug1342')->insert([ + 'a1' => 24, + 'a2' => 48, +]); + +Assert::same($insertedRows->a1, 24); +Assert::same($insertedRows->a2, 48); diff --git a/tests/Database/files/mysql-nette_test4.sql b/tests/Database/files/mysql-nette_test4.sql new file mode 100644 index 000000000..754ab2f1c --- /dev/null +++ b/tests/Database/files/mysql-nette_test4.sql @@ -0,0 +1,33 @@ +DROP DATABASE IF EXISTS nette_test; +CREATE DATABASE nette_test; +USE nette_test; + +CREATE TABLE simple_pk_autoincrement ( + identifier1 int NOT NULL AUTO_INCREMENT, + note varchar(100), + PRIMARY KEY (identifier1) +) ENGINE=InnoDB; + +CREATE TABLE simple_pk_no_autoincrement ( + identifier1 int NOT NULL, + note varchar(100), + PRIMARY KEY (identifier1) +) ENGINE=InnoDB; + +CREATE TABLE multi_pk_no_autoincrement ( + identifier1 int NOT NULL, + identifier2 int NOT NULL, + note varchar(100), + PRIMARY KEY (identifier1, identifier2) +) ENGINE=InnoDB; + +CREATE TABLE multi_pk_autoincrement( + identifier1 int NOT NULL AUTO_INCREMENT, + identifier2 int NOT NULL, + note varchar(100), + PRIMARY KEY (identifier1, identifier2) +) ENGINE=InnoDB; + +CREATE TABLE no_pk ( + note varchar(100) +) ENGINE=InnoDB; diff --git a/tests/Database/files/pgsql-nette_test4.sql b/tests/Database/files/pgsql-nette_test4.sql new file mode 100644 index 000000000..b45fa6850 --- /dev/null +++ b/tests/Database/files/pgsql-nette_test4.sql @@ -0,0 +1,32 @@ +DROP SCHEMA IF EXISTS public CASCADE; +CREATE SCHEMA public; + +CREATE TABLE simple_pk_autoincrement ( + identifier1 serial NOT NULL, + note varchar(100), + PRIMARY KEY (identifier1) +); + +CREATE TABLE simple_pk_no_autoincrement ( + identifier1 int NOT NULL, + note varchar(100), + PRIMARY KEY (identifier1) +); + +CREATE TABLE multi_pk_no_autoincrement ( + identifier1 int NOT NULL, + identifier2 int NOT NULL, + note varchar(100), + PRIMARY KEY (identifier1, identifier2) +); + +CREATE TABLE multi_pk_autoincrement( + identifier1 serial NOT NULL, + identifier2 int NOT NULL, + note varchar(100), + PRIMARY KEY (identifier1, identifier2) +); + +CREATE TABLE no_pk ( + note varchar(100) +); diff --git a/tests/Database/files/sqlite-nette_test4.sql b/tests/Database/files/sqlite-nette_test4.sql new file mode 100644 index 000000000..e4ffe7889 --- /dev/null +++ b/tests/Database/files/sqlite-nette_test4.sql @@ -0,0 +1,27 @@ +DROP TABLE IF EXISTS simple_pk_autoincrement; +DROP TABLE IF EXISTS simple_pk_no_autoincrement; +DROP TABLE IF EXISTS multi_pk_no_autoincrement; +DROP TABLE IF EXISTS multi_pk_autoincrement; +DROP TABLE IF EXISTS no_pk; + +CREATE TABLE simple_pk_autoincrement ( + identifier1 integer PRIMARY KEY AUTOINCREMENT, + note varchar(100) +); + +CREATE TABLE simple_pk_no_autoincrement ( + identifier1 int NOT NULL, + note varchar(100), + PRIMARY KEY (identifier1) +); + +CREATE TABLE multi_pk_no_autoincrement ( + identifier1 int NOT NULL, + identifier2 int NOT NULL, + note varchar(100), + PRIMARY KEY (identifier1, identifier2) +); + +CREATE TABLE no_pk ( + note varchar(100) +); diff --git a/tests/Database/files/sqlsrv-nette_test4.sql b/tests/Database/files/sqlsrv-nette_test4.sql new file mode 100644 index 000000000..b9b5a86c7 --- /dev/null +++ b/tests/Database/files/sqlsrv-nette_test4.sql @@ -0,0 +1,36 @@ +IF OBJECT_ID('simple_pk_autoincrement', 'U') IS NOT NULL DROP TABLE simple_pk_autoincrement; +IF OBJECT_ID('simple_pk_no_autoincrement', 'U') IS NOT NULL DROP TABLE simple_pk_no_autoincrement; +IF OBJECT_ID('multi_pk_no_autoincrement', 'U') IS NOT NULL DROP TABLE multi_pk_no_autoincrement; +IF OBJECT_ID('multi_pk_autoincrement', 'U') IS NOT NULL DROP TABLE multi_pk_autoincrement; +IF OBJECT_ID('no_pk', 'U') IS NOT NULL DROP TABLE no_pk; + + +CREATE TABLE simple_pk_autoincrement ( + identifier1 int NOT NULL IDENTITY(1,1), + note varchar(100), + PRIMARY KEY (identifier1) +); + +CREATE TABLE simple_pk_no_autoincrement ( + identifier1 int NOT NULL, + note varchar(100), + PRIMARY KEY (identifier1) +); + +CREATE TABLE multi_pk_no_autoincrement ( + identifier1 int NOT NULL, + identifier2 int NOT NULL, + note varchar(100) +); +ALTER TABLE multi_pk_no_autoincrement ADD CONSTRAINT PK_multi_pk_no_autoincrement PRIMARY KEY CLUSTERED (identifier1, identifier2); + +CREATE TABLE multi_pk_autoincrement( + identifier1 int NOT NULL IDENTITY(1,1), + identifier2 int NOT NULL, + note varchar(100) +); +ALTER TABLE multi_pk_autoincrement ADD CONSTRAINT PK_multi_pk_autoincrement PRIMARY KEY CLUSTERED (identifier1, identifier2); + +CREATE TABLE no_pk ( + note varchar(100) +);