Skip to content

Loading…

DB - related() on GroupedSelection #653

Closed
wants to merge 5 commits into from

2 participants

@juzna
Nette Foundation member

i.e. when you apply related() twice, it won't work.

Suppose two companies, each with one author, each with two books. To output them all, you'd write three foreach loops (see the test case). There, the inner foreach doesn't work properly and will return items only for the first author.

@juzna juzna commented on the diff
Nette/Database/Table/GroupedSelection.php
@@ -226,4 +226,17 @@ protected function execute()
}
}
+
+
+ protected function getAllFetchedKeys() {
+ $hash = md5($this->getSql() . json_encode($this->parameters));
+ $referencing = & $this->referencing[$hash];
+ if ($referencing === NULL) {
+ // i believe this should never happen
@juzna Nette Foundation member
juzna added a note

Need a hint about this situation. Anyone?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@juzna juzna commented on the diff
tests/Nette/Database/Table.related().phpt
((11 lines not shown))
+ foreach($company->related('author') as $author) {
+ $log[] = "Author $author->id";
+ foreach($author->related('book') as $book) {
+ $log[] = "Book $book->id";
+ }
+ }
+}
+
+Assert::same( array(
+ "Company 41",
+ "Author 11",
+ "Book 1",
+ "Book 2",
+ "Company 42",
+ "Author 12",
+ "Book 3",
@juzna Nette Foundation member
juzna added a note

Without fix, it won't print out these books (or any following)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@juzna juzna commented on the diff
tests/Nette/Database/Table.related().phpt
((22 lines not shown))
+ "Book 1",
+ "Book 2",
+ "Company 42",
+ "Author 12",
+ "Book 3",
+ "Book 4",
+ "Company 43",
+ "Author 13",
+), $log );
+
+
+
+// related() on selection where first row has no related items
+$log = array();
+foreach ($connection->table('author')->order('id DESC') as $author) {
+ foreach($author->related('book') as $book) {
@juzna Nette Foundation member
juzna added a note

This is lovely! If the first author has no books, then next line will crash.
(i.e. the first item has to have subitems for related() to work properly :D )

Btw error is: Error: Invalid argument supplied for foreach()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@juzna juzna Db: fixed very strange bug with related()
test included in previous commit

Btw: not sure how good this pach is, but makes some good sense and works
c702162
@hrach

WTF? :D so much confused commits? :D
See my dev branch https://github.com/hrach/nette/tree/f-database-refactoring
a this commit should fix it: hrach@46f0fc9

@juzna
Nette Foundation member

Yours is big refactoring, this was just a quick fix. But yep, not the best solution.

Your branch seems to work, but changes sooo much. Not sure when/if it'll be merged, i.e. useful for everyone.

@hrach

Must be :D And it's seems weird only in diff. Basically it's just decomposition building sql to another class.
The information it works sounds great, so, both bugs are there fixed?
However, the referenced commit should be easy (4 lines) fix, which should work also on actual master.

From my point of you are doing in your fix some magic, which I dont undestand :D

@juzna
Nette Foundation member

Selection loads data into $rows, which is then used when searching for related entries (both related and relating).
GroupedSelection lazily moves data from $rows into $referencing, which is an array aggregated by a particular column. Here comes the problem, when methods in Selection expect data in $rows, where it is not anymore ;)

hope it's little bit more clear now

@hrach

No, you are not right. GroupedSelection dont move data from rows. It only aggregates data by particular column into $data. (cached by referencing).
https://github.com/hrach/nette/blob/f-database-refactoring/Nette/Database/Table/GroupedSelection.php#L165

@juzna
Nette Foundation member

Your version ;) Nette's master branch behaves differently. Never mind though, Db needs to be refactored because these are only hacks to fix artificial problems.

@juzna juzna closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 15, 2012
  1. @juzna
  2. @juzna
  3. @juzna

    missing phpdoc

    juzna committed
Commits on May 16, 2012
  1. @juzna
  2. @juzna

    Db: fixed very strange bug with related()

    juzna committed
    test included in previous commit
    
    Btw: not sure how good this pach is, but makes some good sense and works
View
26 Nette/Database/Table/GroupedSelection.php
@@ -226,4 +226,30 @@ protected function execute()
}
}
+
+
+ protected function getAllFetchedKeys() {
+ $hash = md5($this->getSql() . json_encode($this->parameters));
+ $referencing = & $this->referencing[$hash];
+ if ($referencing === NULL) {
+ // i believe this should never happen
@juzna Nette Foundation member
juzna added a note

Need a hint about this situation. Anyone?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ }
+
+ // remove non-arrays, take 2nd level keys, merge, unique
+ return array_unique(call_user_func_array('array_merge', array_map('array_keys', array_filter($referencing, 'is_array'))));
+ }
+
+
+
+ protected function getAllFetchedRows() {
+ $hash = md5($this->getSql() . json_encode($this->parameters));
+ $referencing = & $this->referencing[$hash];
+ if ($referencing === NULL) {
+ // i believe this should never happen
+ }
+
+ // remove non-arrays, merge, unique
+ return array_unique(call_user_func_array('array_merge', array_filter($referencing, 'is_array')));
+ }
+
}
View
20 Nette/Database/Table/Selection.php
@@ -23,6 +23,9 @@
* @author Jakub Vrana
* @author Jan Skrasek
*
+ * @property-read Nette\Database\Connection $connection
+ * @property-read string $name
+ * @property-read string $primary
* @property-read string $sql
*/
class Selection extends Nette\Object implements \Iterator, \ArrayAccess, \Countable
@@ -705,7 +708,7 @@ public function getReferencedTable($table, $column, $checkReferenceNewKeys = FAL
$referenced = & $this->referenced["$table.$column"];
if ($referenced === NULL || $checkReferenceNewKeys || $this->checkReferenceNewKeys) {
$keys = array();
- foreach ($this->rows as $row) {
+ foreach ($this->getAllFetchedRows() as $row) {
if ($row[$column] === NULL)
continue;
@@ -746,11 +749,24 @@ public function getReferencingTable($table, $column, $active = NULL, $forceNewIn
$referencing = new GroupedSelection($table, $this, $column);
}
- return $referencing->setActive($active)->where("$table.$column", array_keys((array) $this->rows));
+ return $referencing->setActive($active)->where("$table.$column", $this->getAllFetchedKeys());
}
+ /** @internal */
+ protected function getAllFetchedKeys() {
+ return array_keys((array) $this->rows);
+ }
+
+
+
+ /** @internal */
+ protected function getAllFetchedRows() {
+ return $this->rows;
+ }
+
+
/********************* interface Iterator ****************d*g**/
View
11 tests/Nette/Database/Reflection.MySQL.phpt
@@ -79,6 +79,17 @@ Assert::same( array(
'autoincrement' => FALSE,
'primary' => FALSE,
),
+ array(
+ 'name' => 'company_id',
+ 'table' => 'author',
+ 'nativetype' => 'INT',
+ 'size' => 11,
+ 'unsigned' => FALSE,
+ 'nullable' => TRUE,
+ 'default' => NULL,
+ 'autoincrement' => FALSE,
+ 'primary' => FALSE,
+ ),
), $columns );
View
1 tests/Nette/Database/Table.insert().phpt
@@ -38,6 +38,7 @@ Assert::equal(array(
'name' => 'Catelyn Stark',
'web' => 'http://example.com',
'born' => new \DateTime('2011-11-11'),
+ 'company_id' => NULL,
), $catelynStark->toArray());
View
43 tests/Nette/Database/Table.related().phpt
@@ -48,8 +48,8 @@ foreach($connection->table('author')->order('id') as $author) {
$counts2[] = $author->related('book.author_id')->where('translator_id', NULL)->count('id');
}
-Assert::same(array(2, 2), $counts1);
-Assert::same(array(1, 0), $counts2);
+Assert::same(array(2, 2, 0), $counts1);
+Assert::same(array(1, 0, 0), $counts2);
@@ -61,3 +61,42 @@ Assert::false($books->fetch());
Assert::same('1001 tipu a triku pro PHP', $author->related('book')->fetch()->title);
Assert::same('JUSH', $author->related('book', NULL, TRUE)->where('translator_id', NULL)->fetch()->title);
+
+
+
+// related() applied twice
+$log = array();
+foreach ($connection->table('company') as $company) {
+ $log[] = "Company $company->id";
+ foreach($company->related('author') as $author) {
+ $log[] = "Author $author->id";
+ foreach($author->related('book') as $book) {
+ $log[] = "Book $book->id";
+ }
+ }
+}
+
+Assert::same( array(
+ "Company 41",
+ "Author 11",
+ "Book 1",
+ "Book 2",
+ "Company 42",
+ "Author 12",
+ "Book 3",
@juzna Nette Foundation member
juzna added a note

Without fix, it won't print out these books (or any following)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ "Book 4",
+ "Company 43",
+ "Author 13",
+), $log );
+
+
+
+// related() on selection where first row has no related items
+$log = array();
+foreach ($connection->table('author')->order('id DESC') as $author) {
+ foreach($author->related('book') as $book) {
@juzna Nette Foundation member
juzna added a note

This is lovely! If the first author has no books, then next line will crash.
(i.e. the first item has to have subitems for related() to work properly :D )

Btw error is: Error: Invalid argument supplied for foreach()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ $log[] = $book->author->id;
+ }
+}
+Assert::same( array(12, 12, 11, 11), $log );
+
View
23 tests/Nette/Database/nette_test1.sql
@@ -6,17 +6,32 @@ USE nette_test;
SET FOREIGN_KEY_CHECKS = 0;
+DROP TABLE IF EXISTS company;
+CREATE TABLE company (
+ id int NOT NULL AUTO_INCREMENT,
+ name varchar(30) NOT NULL,
+ web varchar(100) NOT NULL,
+ PRIMARY KEY(id)
+) AUTO_INCREMENT=44;
+
+INSERT INTO company (id, name, web) VALUES (41, 'Nette Foundation', 'http://nette.org');
+INSERT INTO company (id, name, web) VALUES (42, 'Facebook', 'http://facebook.com');
+INSERT INTO company (id, name, web) VALUES (43, 'juznasoft', 'http://juzna.cz');
+
DROP TABLE IF EXISTS author;
CREATE TABLE author (
id int NOT NULL AUTO_INCREMENT,
name varchar(30) NOT NULL,
web varchar(100) NOT NULL,
born date DEFAULT NULL,
- PRIMARY KEY(id)
-) AUTO_INCREMENT=13;
+ company_id int DEFAULT NULL,
+ PRIMARY KEY(id),
+ CONSTRAINT author_company FOREIGN KEY (company_id) REFERENCES company (id)
+) AUTO_INCREMENT=14;
-INSERT INTO author (id, name, web, born) VALUES (11, 'Jakub Vrana', 'http://www.vrana.cz/', NULL);
-INSERT INTO author (id, name, web, born) VALUES (12, 'David Grudl', 'http://davidgrudl.com/', NULL);
+INSERT INTO author (id, name, web, born, company_id) VALUES (11, 'Jakub Vrana', 'http://www.vrana.cz/', NULL, 41);
+INSERT INTO author (id, name, web, born, company_id) VALUES (12, 'David Grudl', 'http://davidgrudl.com/', NULL, 42);
+INSERT INTO author (id, name, web, born, company_id) VALUES (13, 'Jan Dolecek', 'http://juzna.cz/', NULL, 43);
DROP TABLE IF EXISTS tag;
CREATE TABLE tag (
Something went wrong with that request. Please try again.