From d6e6ced9e3f2b7d869b9434364d2aea41ef71868 Mon Sep 17 00:00:00 2001 From: hrach Date: Thu, 10 Jul 2014 09:33:04 +0200 Subject: [PATCH 1/8] DI: fixed autowiring propper connection for reflection [closes #16] --- src/Bridges/DatabaseDI/DatabaseExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/DatabaseDI/DatabaseExtension.php b/src/Bridges/DatabaseDI/DatabaseExtension.php index f7f16f3fc..f59708e65 100644 --- a/src/Bridges/DatabaseDI/DatabaseExtension.php +++ b/src/Bridges/DatabaseDI/DatabaseExtension.php @@ -27,7 +27,7 @@ class DatabaseExtension extends Nette\DI\CompilerExtension 'options' => NULL, 'debugger' => TRUE, 'explain' => TRUE, - 'reflection' => 'Nette\Database\Reflection\DiscoveredReflection', + 'reflection' => 'discovered', // Nette\Database\Reflection\DiscoveredReflection 'autowired' => NULL, ); From 6ef61847463cd375eb492810a36ee638195a21b4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 24 Jul 2014 04:06:47 +0200 Subject: [PATCH 2/8] SqlBuilder: added support for NOT with NULL --- src/Database/Table/SqlBuilder.php | 8 ++++++-- tests/Database/Table/SqlBuilder.addWhere().phpt | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Database/Table/SqlBuilder.php b/src/Database/Table/SqlBuilder.php index 4fda8f124..7001dbdd0 100644 --- a/src/Database/Table/SqlBuilder.php +++ b/src/Database/Table/SqlBuilder.php @@ -222,10 +222,14 @@ public function addWhere($condition, $parameters = array()) $hasOperator = ($match[1][0] === '?' && $match[1][1] === 0) ? TRUE : !empty($match[2][0]); if ($arg === NULL) { + $replace = 'IS NULL'; if ($hasOperator) { - throw new Nette\InvalidArgumentException('Column operator does not accept NULL argument.'); + if (trim($match[2][0]) === 'NOT') { + $replace = 'IS NOT NULL'; + } else { + throw new Nette\InvalidArgumentException('Column operator does not accept NULL argument.'); + } } - $replace = 'IS NULL'; } elseif (is_array($arg) || $arg instanceof Selection) { if ($hasOperator) { if (trim($match[2][0]) === 'NOT') { diff --git a/tests/Database/Table/SqlBuilder.addWhere().phpt b/tests/Database/Table/SqlBuilder.addWhere().phpt index 30796b834..1fda0b5cd 100644 --- a/tests/Database/Table/SqlBuilder.addWhere().phpt +++ b/tests/Database/Table/SqlBuilder.addWhere().phpt @@ -117,10 +117,11 @@ test(function() use ($connection, $reflection) { // tests multiline condition test(function() use ($context, $connection, $reflection) { // tests NOT $sqlBuilder = new SqlBuilder('book', $connection, $reflection); $sqlBuilder->addWhere('id NOT', array(1, 2)); + $sqlBuilder->addWhere('id NOT', NULL); $sqlBuilder->addWhere('id NOT', $context->table('book')->select('id')); Assert::equal(reformat(array( - 'mysql' => 'SELECT * FROM `book` WHERE (`id` NOT IN (?)) AND (`id` NOT IN (?))', - 'SELECT * FROM [book] WHERE ([id] NOT IN (?)) AND ([id] NOT IN (SELECT [id] FROM [book]))', + 'mysql' => 'SELECT * FROM `book` WHERE (`id` NOT IN (?)) AND (`id` IS NOT NULL) AND (`id` NOT IN (?))', + 'SELECT * FROM [book] WHERE ([id] NOT IN (?)) AND ([id] IS NOT NULL) AND ([id] NOT IN (SELECT [id] FROM [book]))', )), $sqlBuilder->buildSelectQuery()); }); From 88afc452ba5a9952a1747ebc4c7e5a15883633c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20H=C5=AFla?= Date: Wed, 16 Jul 2014 13:22:58 +0200 Subject: [PATCH 3/8] PgSqlDriver: fixed reflection for tables with the same name across the schemas Construct ::regclass ensures selection of table in respect to 'search_path' setting. --- src/Database/Drivers/PgSqlDriver.php | 22 +++++------- tests/Database/Reflection.postgre.phpt | 49 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 tests/Database/Reflection.postgre.phpt diff --git a/src/Database/Drivers/PgSqlDriver.php b/src/Database/Drivers/PgSqlDriver.php index d83ddb25d..636dfbd30 100644 --- a/src/Database/Drivers/PgSqlDriver.php +++ b/src/Database/Drivers/PgSqlDriver.php @@ -101,7 +101,7 @@ public function getTables() { $tables = array(); foreach ($this->connection->query(" - SELECT + SELECT DISTINCT ON (c.relname) c.relname::varchar AS name, c.relkind = 'v' AS view FROM @@ -109,7 +109,7 @@ public function getTables() JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'v') - AND ARRAY[n.nspname] <@ pg_catalog.current_schemas(FALSE) + AND n.nspname = ANY (pg_catalog.current_schemas(FALSE)) ORDER BY c.relname ") as $row) { @@ -141,14 +141,12 @@ public function getColumns($table) FROM pg_catalog.pg_attribute AS a JOIN pg_catalog.pg_class AS c ON a.attrelid = c.oid - JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace JOIN pg_catalog.pg_type AS t ON a.atttypid = t.oid LEFT JOIN pg_catalog.pg_attrdef AS ad ON ad.adrelid = c.oid AND ad.adnum = a.attnum - LEFT JOIN pg_catalog.pg_constraint AS co ON co.connamespace = n.oid AND contype = 'p' AND co.conrelid = c.oid AND a.attnum = ANY(co.conkey) + LEFT JOIN pg_catalog.pg_constraint AS co ON co.connamespace = c.relnamespace AND contype = 'p' AND co.conrelid = c.oid AND a.attnum = ANY(co.conkey) WHERE c.relkind IN ('r', 'v') - AND c.relname::varchar = {$this->connection->quote($table)} - AND ARRAY[n.nspname] <@ pg_catalog.current_schemas(FALSE) + AND c.oid = {$this->connection->quote($this->delimite($table))}::regclass AND a.attnum > 0 AND NOT a.attisdropped ORDER BY @@ -179,14 +177,12 @@ public function getIndexes($table) a.attname::varchar AS column FROM pg_catalog.pg_class AS c1 - JOIN pg_catalog.pg_namespace AS n ON c1.relnamespace = n.oid JOIN pg_catalog.pg_index AS i ON c1.oid = i.indrelid JOIN pg_catalog.pg_class AS c2 ON i.indexrelid = c2.oid LEFT JOIN pg_catalog.pg_attribute AS a ON c1.oid = a.attrelid AND a.attnum = ANY(i.indkey) WHERE - ARRAY[n.nspname] <@ pg_catalog.current_schemas(FALSE) - AND c1.relkind = 'r' - AND c1.relname = {$this->connection->quote($table)} + c1.relkind = 'r' + AND c1.oid = {$this->connection->quote($this->delimite($table))}::regclass ") as $row) { $indexes[$row['name']]['name'] = $row['name']; $indexes[$row['name']]['unique'] = $row['unique']; @@ -212,15 +208,13 @@ public function getForeignKeys($table) af.attname::varchar AS foreign FROM pg_catalog.pg_constraint AS co - JOIN pg_catalog.pg_namespace AS n ON co.connamespace = n.oid JOIN pg_catalog.pg_class AS cl ON co.conrelid = cl.oid JOIN pg_catalog.pg_class AS cf ON co.confrelid = cf.oid JOIN pg_catalog.pg_attribute AS al ON al.attrelid = cl.oid AND al.attnum = co.conkey[1] JOIN pg_catalog.pg_attribute AS af ON af.attrelid = cf.oid AND af.attnum = co.confkey[1] WHERE - ARRAY[n.nspname] <@ pg_catalog.current_schemas(FALSE) - AND co.contype = 'f' - AND cl.relname = {$this->connection->quote($table)} + co.contype = 'f' + AND cl.oid = {$this->connection->quote($this->delimite($table))}::regclass ")->fetchAll(); } diff --git a/tests/Database/Reflection.postgre.phpt b/tests/Database/Reflection.postgre.phpt new file mode 100644 index 000000000..7f326601c --- /dev/null +++ b/tests/Database/Reflection.postgre.phpt @@ -0,0 +1,49 @@ +getSupplementalDriver(); + +function filter($columns) { + array_walk($columns, function(& $item) { $item = $item['name']; }); + return $columns; +} + + +$connection->query('SET search_path TO one, two'); +Assert::same(array('master', 'slave'), filter($driver->getTables())); +Assert::same(array('one_id'), filter($driver->getColumns('master'))); +Assert::same(array('one_master_pkey'), filter($driver->getIndexes('master'))); +Assert::same(array('one_slave_fk'), filter($driver->getForeignKeys('slave'))); + +$connection->query('SET search_path TO two, one'); +Assert::same(array('master', 'slave'), filter($driver->getTables())); +Assert::same(array('two_id'), filter($driver->getColumns('master'))); +Assert::same(array('two_master_pkey'), filter($driver->getIndexes('master'))); +Assert::same(array('two_slave_fk'), filter($driver->getForeignKeys('slave'))); From 38b8d479eb09db0ca1e0e148ae118cd8621ca05b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20H=C5=AFla?= Date: Wed, 16 Jul 2014 13:37:00 +0200 Subject: [PATCH 4/8] PgSqlDriver: getForeignKeys() returns FKs from current schemas only --- src/Database/Drivers/PgSqlDriver.php | 2 ++ tests/Database/Reflection.postgre.phpt | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/Database/Drivers/PgSqlDriver.php b/src/Database/Drivers/PgSqlDriver.php index 636dfbd30..d6592c8ac 100644 --- a/src/Database/Drivers/PgSqlDriver.php +++ b/src/Database/Drivers/PgSqlDriver.php @@ -210,11 +210,13 @@ public function getForeignKeys($table) pg_catalog.pg_constraint AS co JOIN pg_catalog.pg_class AS cl ON co.conrelid = cl.oid JOIN pg_catalog.pg_class AS cf ON co.confrelid = cf.oid + JOIN pg_catalog.pg_namespace AS nf ON nf.oid = cf.relnamespace JOIN pg_catalog.pg_attribute AS al ON al.attrelid = cl.oid AND al.attnum = co.conkey[1] JOIN pg_catalog.pg_attribute AS af ON af.attrelid = cf.oid AND af.attnum = co.confkey[1] WHERE co.contype = 'f' AND cl.oid = {$this->connection->quote($this->delimite($table))}::regclass + AND nf.nspname = ANY (pg_catalog.current_schemas(FALSE)) ")->fetchAll(); } diff --git a/tests/Database/Reflection.postgre.phpt b/tests/Database/Reflection.postgre.phpt index 7f326601c..c32c6ce2b 100644 --- a/tests/Database/Reflection.postgre.phpt +++ b/tests/Database/Reflection.postgre.phpt @@ -47,3 +47,10 @@ Assert::same(array('master', 'slave'), filter($driver->getTables())); Assert::same(array('two_id'), filter($driver->getColumns('master'))); Assert::same(array('two_master_pkey'), filter($driver->getIndexes('master'))); Assert::same(array('two_slave_fk'), filter($driver->getForeignKeys('slave'))); + +// Limit foreign keys for current schemas only +$connection->query('ALTER TABLE "one"."slave" ADD CONSTRAINT "one_two_fk" FOREIGN KEY ("one_id") REFERENCES "two"."master"("two_id")'); +$connection->query('SET search_path TO one'); +Assert::same(array('one_slave_fk'), filter($driver->getForeignKeys('slave'))); +$connection->query('SET search_path TO one, two'); +Assert::same(array('one_slave_fk', 'one_two_fk'), filter($driver->getForeignKeys('slave'))); From 02f052a1d4e3a6c3d318d6d56f7f376c55d15e09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20H=C5=AFla?= Date: Fri, 18 Jul 2014 11:06:16 +0200 Subject: [PATCH 5/8] PgSqlDriver: reflection supports table in FQN --- src/Database/Drivers/PgSqlDriver.php | 20 +++++++++++++++++--- tests/Database/Reflection.postgre.phpt | 20 ++++++++++++++------ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/Database/Drivers/PgSqlDriver.php b/src/Database/Drivers/PgSqlDriver.php index d6592c8ac..481eca761 100644 --- a/src/Database/Drivers/PgSqlDriver.php +++ b/src/Database/Drivers/PgSqlDriver.php @@ -146,7 +146,7 @@ public function getColumns($table) LEFT JOIN pg_catalog.pg_constraint AS co ON co.connamespace = c.relnamespace AND contype = 'p' AND co.conrelid = c.oid AND a.attnum = ANY(co.conkey) WHERE c.relkind IN ('r', 'v') - AND c.oid = {$this->connection->quote($this->delimite($table))}::regclass + AND c.oid = {$this->connection->quote($this->delimiteFQN($table))}::regclass AND a.attnum > 0 AND NOT a.attisdropped ORDER BY @@ -182,7 +182,7 @@ public function getIndexes($table) LEFT JOIN pg_catalog.pg_attribute AS a ON c1.oid = a.attrelid AND a.attnum = ANY(i.indkey) WHERE c1.relkind = 'r' - AND c1.oid = {$this->connection->quote($this->delimite($table))}::regclass + AND c1.oid = {$this->connection->quote($this->delimiteFQN($table))}::regclass ") as $row) { $indexes[$row['name']]['name'] = $row['name']; $indexes[$row['name']]['unique'] = $row['unique']; @@ -215,7 +215,7 @@ public function getForeignKeys($table) JOIN pg_catalog.pg_attribute AS af ON af.attrelid = cf.oid AND af.attnum = co.confkey[1] WHERE co.contype = 'f' - AND cl.oid = {$this->connection->quote($this->delimite($table))}::regclass + AND cl.oid = {$this->connection->quote($this->delimiteFQN($table))}::regclass AND nf.nspname = ANY (pg_catalog.current_schemas(FALSE)) ")->fetchAll(); } @@ -239,4 +239,18 @@ public function isSupported($item) return $item === self::SUPPORT_SEQUENCE || $item === self::SUPPORT_SUBSELECT; } + + /** + * Converts: schema.name => "schema"."name" + * @param string + * @return string + */ + private function delimiteFQN($name) + { + $_this = $this; + return implode('.', array_map(function($part) use ($_this) { + return $_this->delimite($part); + }, explode('.', $name))); + } + } diff --git a/tests/Database/Reflection.postgre.phpt b/tests/Database/Reflection.postgre.phpt index c32c6ce2b..aee3de9c4 100644 --- a/tests/Database/Reflection.postgre.phpt +++ b/tests/Database/Reflection.postgre.phpt @@ -1,7 +1,7 @@ query('SET search_path TO one, two'); Assert::same(array('master', 'slave'), filter($driver->getTables())); Assert::same(array('one_id'), filter($driver->getColumns('master'))); @@ -48,6 +49,13 @@ Assert::same(array('two_id'), filter($driver->getColumns('master'))); Assert::same(array('two_master_pkey'), filter($driver->getIndexes('master'))); Assert::same(array('two_slave_fk'), filter($driver->getForeignKeys('slave'))); + +// Reflection for FQN +Assert::same(array('one_id'), filter($driver->getColumns('one.master'))); +Assert::same(array('one_master_pkey'), filter($driver->getIndexes('one.master'))); +Assert::same(array('one_slave_fk'), filter($driver->getForeignKeys('one.slave'))); + + // Limit foreign keys for current schemas only $connection->query('ALTER TABLE "one"."slave" ADD CONSTRAINT "one_two_fk" FOREIGN KEY ("one_id") REFERENCES "two"."master"("two_id")'); $connection->query('SET search_path TO one'); From de44319b4cca3e5cfcce8ec31b3cde9b77029b1f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 11 Aug 2014 15:12:19 +0200 Subject: [PATCH 6/8] PgSqlDriver: removed unnecessary closure --- src/Database/Drivers/PgSqlDriver.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Database/Drivers/PgSqlDriver.php b/src/Database/Drivers/PgSqlDriver.php index 481eca761..5601b1788 100644 --- a/src/Database/Drivers/PgSqlDriver.php +++ b/src/Database/Drivers/PgSqlDriver.php @@ -243,14 +243,11 @@ public function isSupported($item) /** * Converts: schema.name => "schema"."name" * @param string - * @return string + * @return string */ private function delimiteFQN($name) { - $_this = $this; - return implode('.', array_map(function($part) use ($_this) { - return $_this->delimite($part); - }, explode('.', $name))); + return implode('.', array_map(array($this, 'delimite'), explode('.', $name))); } } From 2a3881d8e2151f1a38a3fd0d51a8c6843d8cd72a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 18 Aug 2014 00:45:51 +0200 Subject: [PATCH 7/8] .travis: added code checker --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index aa4dff01e..d67f71434 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,15 +10,18 @@ matrix: allow_failures: - php: hhvm -script: vendor/bin/tester tests -s -c tests/php-unix.ini +script: + - vendor/bin/tester tests -s -c tests/php-unix.ini + - php code-checker/src/code-checker.php -d src after_failure: # Print *.actual content - for i in $(find tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done before_script: - # Install Nette Tester + # Install Nette Tester & Code Checker - composer install --no-interaction --dev --prefer-source + - composer create-project nette/code-checker code-checker ~2.2 --no-interaction --prefer-source # Create databases.ini - cp ./tests/Database/databases.sample.ini ./tests/Database/databases.ini From 5b7bf41ba57f1f809823555e4630030f4ebc2a05 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 24 Aug 2014 20:40:35 +0200 Subject: [PATCH 8/8] removed dead & unused code --- src/Bridges/DatabaseDI/DatabaseExtension.php | 3 +-- src/Database/Drivers/MsSqlDriver.php | 8 -------- src/Database/Drivers/MySqlDriver.php | 16 ---------------- src/Database/Drivers/OdbcDriver.php | 9 --------- src/Database/Drivers/SqliteDriver.php | 1 - src/Database/Helpers.php | 1 - src/Database/Table/ActiveRow.php | 4 ---- 7 files changed, 1 insertion(+), 41 deletions(-) diff --git a/src/Bridges/DatabaseDI/DatabaseExtension.php b/src/Bridges/DatabaseDI/DatabaseExtension.php index f59708e65..053318401 100644 --- a/src/Bridges/DatabaseDI/DatabaseExtension.php +++ b/src/Bridges/DatabaseDI/DatabaseExtension.php @@ -7,8 +7,7 @@ namespace Nette\Bridges\DatabaseDI; -use Nette, - Nette\DI\ContainerBuilder; +use Nette; /** diff --git a/src/Database/Drivers/MsSqlDriver.php b/src/Database/Drivers/MsSqlDriver.php index 3413d67e7..aae4e5f25 100644 --- a/src/Database/Drivers/MsSqlDriver.php +++ b/src/Database/Drivers/MsSqlDriver.php @@ -17,14 +17,6 @@ */ class MsSqlDriver extends Nette\Object implements Nette\Database\ISupplementalDriver { - /** @var Nette\Database\Connection */ - private $connection; - - - public function __construct(Nette\Database\Connection $connection, array $options) - { - $this->connection = $connection; - } /********************* SQL ****************d*g**/ diff --git a/src/Database/Drivers/MySqlDriver.php b/src/Database/Drivers/MySqlDriver.php index 48f85fc35..6f71e1143 100644 --- a/src/Database/Drivers/MySqlDriver.php +++ b/src/Database/Drivers/MySqlDriver.php @@ -114,11 +114,6 @@ public function normalizeRow($row) */ public function getTables() { - /*$this->connection->query(" - SELECT TABLE_NAME as name, TABLE_TYPE = 'VIEW' as view - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_SCHEMA = DATABASE() - ");*/ $tables = array(); foreach ($this->connection->query('SHOW FULL TABLES') as $row) { $tables[] = array( @@ -135,11 +130,6 @@ public function getTables() */ public function getColumns($table) { - /*$this->connection->query(" - SELECT * - FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_NAME = {$this->connection->quote($table)} AND TABLE_SCHEMA = DATABASE() - ");*/ $columns = array(); foreach ($this->connection->query('SHOW FULL COLUMNS FROM ' . $this->delimite($table)) as $row) { $type = explode('(', $row['Type']); @@ -165,12 +155,6 @@ public function getColumns($table) */ public function getIndexes($table) { - /*$this->connection->query(" - SELECT * - FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE - WHERE TABLE_NAME = {$this->connection->quote($table)} AND TABLE_SCHEMA = DATABASE() - AND REFERENCED_COLUMN_NAME IS NULL - ");*/ $indexes = array(); foreach ($this->connection->query('SHOW INDEX FROM ' . $this->delimite($table)) as $row) { $indexes[$row['Key_name']]['name'] = $row['Key_name']; diff --git a/src/Database/Drivers/OdbcDriver.php b/src/Database/Drivers/OdbcDriver.php index 717af3080..e7d591b01 100644 --- a/src/Database/Drivers/OdbcDriver.php +++ b/src/Database/Drivers/OdbcDriver.php @@ -17,15 +17,6 @@ */ class OdbcDriver extends Nette\Object implements Nette\Database\ISupplementalDriver { - /** @var Nette\Database\Connection */ - private $connection; - - - public function __construct(Nette\Database\Connection $connection, array $options) - { - $this->connection = $connection; - } - /********************* SQL ****************d*g**/ diff --git a/src/Database/Drivers/SqliteDriver.php b/src/Database/Drivers/SqliteDriver.php index cb3115e6f..d32dff938 100644 --- a/src/Database/Drivers/SqliteDriver.php +++ b/src/Database/Drivers/SqliteDriver.php @@ -28,7 +28,6 @@ public function __construct(Nette\Database\Connection $connection, array $option { $this->connection = $connection; $this->fmtDateTime = isset($options['formatDateTime']) ? $options['formatDateTime'] : 'U'; - //$connection->query('PRAGMA foreign_keys = ON'); } diff --git a/src/Database/Helpers.php b/src/Database/Helpers.php index 8dfc3de92..2e0a6f599 100644 --- a/src/Database/Helpers.php +++ b/src/Database/Helpers.php @@ -57,7 +57,6 @@ public static function dumpResult(ResultSet $result) } echo "\t\n\t\t", $i, "\n"; foreach ($row as $col) { - //if (is_object($col)) $col = $col->__toString(); echo "\t\t", htmlSpecialChars($col), "\n"; } echo "\t\n"; diff --git a/src/Database/Table/ActiveRow.php b/src/Database/Table/ActiveRow.php index 4c946b374..442cd698c 100644 --- a/src/Database/Table/ActiveRow.php +++ b/src/Database/Table/ActiveRow.php @@ -29,9 +29,6 @@ class ActiveRow implements \IteratorAggregate, IRow /** @var bool */ private $dataRefreshed = FALSE; - /** @var bool */ - private $isModified = FALSE; - public function __construct(array $data, Selection $table) { @@ -175,7 +172,6 @@ public function update($data) ->wherePrimary($this->getPrimary()); if ($selection->update($data)) { - $this->isModified = TRUE; $selection->select('*'); if (($row = $selection->fetch()) === FALSE) { throw new Nette\InvalidStateException('Database refetch failed; row does not exist!');