Nette\Database – Nette 2.0 rc1 – problémy
- semtex.989
- Člen | 75
Dnes jsem updatoval rozdělaný projekt, který používá Nette\Database, na 2.0 RC1 a od té doby mám samý problém.
Za prvé se již automaticky neaktivuje ConnectionPanel a musel jsem si ze
zdrojáků vystudovat, že by asi fungovalo navázat na událost $onQuery
v Nette\Database\Connection callback na ConnectionPanel logQuery.
Chápu, že se Nette snaží být méně magické, ale myslím, že by na to
mohlo být příjemnější API.
Pak mám Datagrid, který je nad tabulkou co má ~2000 řádků plus joiny do
jiných tabulek. V NF beta 2 jsem měl vykresleno do 300ms, nyní to trvalo
2 sekundy. Když jsem hledal příčinu, všiml jsem si, že se v RC1 změnily
metody count() a aggregation() v Nette\Database\Table\Selection – pokud
máte náhoudou zrovna Selection co odkazuje třeba na 1000 záznamů, ovlivní
to volání count() (i pokud volám count(‚id‘).
Neměl jsem sílu to dál pitvat, prostě jsem udělal copy/paste kódu
z poslední Bety a světe div se, všechno chodí jak má
<?php
/**
* Executes aggregation function.
* @param string
* @return string
*/
public function aggregation($function)
{
$join = $this->createJoins(implode(',', $this->conditions), TRUE) + $this->createJoins($function);
$query = "SELECT $function FROM $this->delimitedName" . implode($join);
if ($this->where) {
$query .= ' WHERE (' . implode(') AND (', $this->where) . ')';
}
foreach ($this->query($query)->fetch() as $val) {
return $val;
}
}
/**
* Counts number of rows.
* @param string
* @return int
*/
public function count($column = '')
{
if (!$column) {
$this->execute();
return count($this->data);
}
return $this->aggregation("COUNT({$this->tryDelimite($column)})");
}
?>
Myslím, že tohle je ale vyloženě bug a nedělám nic neobvyklého.
Editoval semtex.989 (24. 1. 2012 17:03)
- semtex.989
- Člen | 75
@ViPEr*CZ*: tak to jsem nevěděl. Je to někde zdokumentované?
Btw, přišel jsem na další BC break – ve třídě Nette\Database\Table\Selection byla property $primary public, nyní je private – proč?
- semtex.989
- Člen | 75
@pilec: je to jen read-only.. potřeboval jsem tomu ve specifickém případě nastavit NULL
- semtex.989
- Člen | 75
<?php
$descriptions = $products->related('products_description'); // primary nyní obsahuje language_id
$descriptions->primary = NULL; // hack! - vypíšou se popisky k danému produktu a to u všech jazyků
foreach($descriptions as $description){
...
?>
Ušetří mi vytváření nového selection..
Teď jsem taky zjistil, že pokud sloupec neexistuje, vrátí se NULL (metoda ActiveRow::__get) a jinak se o chybě nedovím, dřív to aspoň házelo noticku.
Editoval semtex.989 (25. 1. 2012 12:48)
- semtex.989
- Člen | 75
Koukám, že taky nejde zavolat metodu $table->related() 2× po sobě, podruhé vrátí FALSE
- David Grudl
- Nette Core | 8228
Příklad použití databáze via config.neon – je to preferovaná cesta, proto jsem ruční aktivaci ConnectionPanelu nezuživatelšťoval.
ad $primary public: u takových hacků se nedá zaručit, že budou fungovat v dalších verzích
ad ostatní: k tomu se bude muset vyjádřit Hrach.
- semtex.989
- Člen | 75
@David Grudl:
- tak o téhle fičuře nevím, na to se podívám
- beru to tedy tak, že primání klíč u Selection zatím nelze legitimně měnit
- hrach
- Člen | 1838
- opakovené volání
related()
jsem otestoval v testovaci db/testech. funguje. poskytni priklad na teto testovaci db, kdy to nefungje. - klidne se budu stourat, kde je chyba v aggregation, respektive v count, nicmene pokud neposkytnes priklad pouziti, tak budu vestit jen z koule. opet idalne na testovaci db.
- ohledne primary, opet nechapu o co se snazich, zkus se trochu vyjadrovat k veci, nez tu plakat, ze neco nejde. „Ušetří mi vytváření nového selection..“ mi trochu zavání… ale budiž, sem s příkladem použití, možná máš špatný návrh db?
- semtex.989
- Člen | 75
- Udělal jsem si přímo v bootstrapu tento jednoduchý test:
Poprvé mi to dumpne row, podruhé FALSE:
<?php
// Create Dependency Injection container from config.neon file
$configurator->addConfig(__DIR__ . '/config.neon');
$container = $configurator->createContainer();
$connection = $container->database;
$selection = $connection->table("products");
$selection->where("products_description.languages_id", 6);
$selection->group("products.id");
$product = $selection->fetch();
$price = $product->related('products_price')->where('pricing_list_id', 1)->fetch();
dump($price);
$price = $product->related('products_price')->where('pricing_list_id', 1)->fetch();
dump($price);
?>
DB je MySQL na lokálu, vazby jsou M:N:
products.id = products_description.products_id
products.id = products_price.products_id
Mám svou vlastní reflexi, aby fungovaly vazby M:N (a tady možná bude problém) – zjednodušil jsem to, odtranil dumpy, ale chyba se projevuje:
<?php
class Reflection extends Nette\Database\Reflection\ConventionalReflection
{
public function getPrimary($table)
{
$r = parent::getPrimary($table);
if($table == "products_description"){
$r = "products_id";
}
if($table == "products_price"){
$r = "products_id";
}
return $r;
}
public function getBelongsToReference($table, $key)
{
$r = parent::getBelongsToReference($table, $key);
if($r[0] == "products_description" || $r[0] == "products_price"){
$r[1] = "id";
}
return $r;
}
?>
2.) Agregace se v RC1 totálně změnila, udělej si schválně selection s tisícem záznamů a zavolej count()
3.) S tím primary se ještě zamyslím, jestli nedělám nějakou ptákovinu. Ale myslím, že pokud nad řádkem zavolám neexistující název sloupce, mlčky vrátit NULL, notabene void, není transparetní. Mělo by to IMHO alespoň vyvolat noticku jako v betě.
Editoval semtex.989 (25. 1. 2012 20:49)
- hrach
- Člen | 1838
- chachá, tak to není chyba, to je feature. volání
related() (respektive execute v daném GroupedSelection) se samozřejmě
cachuje, pokud voláš se stejným vazebním sloupcem… False nevrací, jak
tvrdíš, metoda related(), ale metoda fetch(), a ta ho vrací proto, že už
žádný záznam daný GroupedSelection nemá, protože si ten první už
fetchnul.
Nicméně sem díky tobě objevil jiný bug, ale jeho opravu musím zítra ještě promyslet, ted musim dělat neco jiného.
Edit: vzhledem k necachování GroupedSelection je to ale pravda divné chování, když
$author->related('book') !== $author->related('book')
zkusim to jeste promyslet…
- poskytni kod – funkcionalita se měnit neměla. To, že to nefunguje stejně bude patrně bug.
- …řešíme primární klíč nebo něco jinéh???
Editoval hrach (25. 1. 2012 22:23)
- semtex.989
- Člen | 75
1.) I pokud je to cachované, mohlo by to při každém volání related() resetovat pointer, aby to šlo fetchnout podruhé ne?
2.) Kód:
<?php
<?php
// Create Dependency Injection container from config.neon file
$configurator->addConfig(__DIR__ . '/config.neon');
$container = $configurator->createContainer();
$connection = $container->database;
$selection = $connection->table("products");
$selection->where("products_description.languages_id", 6);
$selection->group("products.id");
count($selection);
?>
3.) Primární klíč bych zatím nechal, asi dělám blbost já. To, že mlčky projde překlep ve sloupci je podle mně horší.
Editoval semtex.989 (26. 1. 2012 9:41)
- hrach
- Člen | 1838
- jsem opravil, za chvili poslu pull
- jestli to dobre chapu, timto dotazem se snazis „vybrat produkty, ktere
maji popisek v urcitem jazyce“. Tedy prevedeno na testovaci db, snazis se
zjistit pocet autoru, kteri maji knihu s prekladatelem.
- chvíli jsem přemýšlel, jak to to mohlo fungovat. Pak jsem si vzpomněl, že máš upravený reflection.. a je jasné proč, protože tam máš naopak vztah 1:N.
- naštěsní Nette\Databse obsahuje něco jako je backjoin, tzn. aktuální řešení bez upravené reflection je:
$authors = $connection->table('author')->where('book:translator_id IS NOT NULL');
echo count($authors);
- pokračování odrážky 2)
- výše zmíněné řešení dělá co chceš – vytáhne všechny data a spočítá řádky
- pokud bys chtěl pouze spoučítaz řádky bez vytažení dat, je to trochu
algorytmicky narocnejsi, problem je v tom, ze count(*) (pripadne
count(author.id) pri pouziti s
group by
nedela to, co chceme. nevraci jednu bunku, ale radky grouple a jako hodnoty jsou vysledky agregacnich funkci. - resenim vyse zmineneho problemu se da v sql vyresit, ze group na dany pripraveny selection nepouziji, a misto toho vyselectuji COUNT(DISCTINCT author.id)
- odrazku vyse by slo zapstat tedy jako
count('DISTINCT author.id')
, coz je silne neintuitivni, zvlaste ve chvili, kdyz uz jsem si „jakoby správný“ select připravil, volání distinct pak na tomto objektu nedává smyslu. upravil (a i opravil) jsem tedy chování tak, že při volání count(*) a nastaveném group by se použije automatickyCOUNT(DISTINCT $groupovaci_sloupec))
, coz je vcelku logicke.- nakonec jsem si to rozmyslel, je to dosti magic, nechal bych to na pripadnou diskuzi do dalsich verzi, v tuto chvili tedy pushnu jenom opravu, aby fungovalo
echo $authors->count('DISTINCT author.id');
toto tedy pak dělá, přesně to, co chceme.
- kdy mlcky prejde preklep ve sloupci?
Editoval hrach (26. 1. 2012 20:30)
- semtex.989
- Člen | 75
ad 1) díky, že se tím vůbec zabýváš. ocenil bych, kdyby ses
obtěžoval pak hodit hlášku, až to bude na gitu
à propos všiml jsem si, že to samé dělá i NotORM. Podle mně je to ale
dost nečekané chování.
ad 2) myslím, že chápu. pokud budeš teď na sobotě, ukažu ti to naživo. nicméně faktem je, že pokud srovnám kód metod count() a aggregation(), je na první pohled relativně rychlejší implementace z NF Beta 2. (klade dotaz, neprochází řádky)
- neumím s githubem abych ti dal link (moje blbost), ale můžeš se sám přesvědčit, že ActiveRow::__get házel noticku (nebo asi dokonce warning)
<?php
trigger_error("Unknown column $key", E_USER_WARNING);
?>
- hrach
- Člen | 1838
- Uz je to v mergnute. 1) by ted ti mela fungovat jak si pozadoval, ackoliv si nejsem jist, jestli je to idealni, ze dva rozdilne objekty sdileji jeden vysledek dotazu, ktery si vzajemne muzou resetovat, respektive cist.
- ke dvojce sem se vyjadril v minulem postu – mezi verzemi neprobihala zadna zmena. myslim si, ze by ses mel odprostit od toho hacknuti reflection a pouzivat backjoin, pak je samozrejme jasne, ze count($object) pocita vyselectnute zaznamy, proto pouzivej count metodu, respektive kod, co sem ti vyse napsal.
- semtex.989
- Člen | 75
Backjoin funguje opravdu dobře.
Ale s tím mým hackem property $primary se to pokusím znovu vysvětlit:
- Mám nějaký fetchnutý ActiveRow (primární klíč je např. 11)
- Zavolám <?php $descriptions = $row->related(‚products_description‘) ?>
- ActiveRow zavolá metodu $selection->getReferencingTable() a předá hodnotu primárního klíče (tedy 11) a vytvoří vytvoří GroupedSelection
- GroupedSelection vrátí řádek s products_id 11 jen jednou, i kdyby v tabulce byl desetkrát, protože záznamy seskupí podle hodnoty primárního klíče.
Editoval semtex.989 (31. 1. 2012 9:33)