Nette\Database – Nette 2.0 rc1 – problémy

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
semtex.989
Člen | 75
+
0
-

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)

ViPEr*CZ*
Člen | 817
+
0
-

Na ConnectionPanel se dá napojit automaticky, ale musí se používat NetteExtension v konfigu.

semtex.989
Člen | 75
+
0
-

@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č?

Jan Endel
Člen | 1016
+
0
-

To by neměl být BC. Poněvadž

semtex.989
Člen | 75
+
0
-

@pilec: je to jen read-only.. potřeboval jsem tomu ve specifickém případě nastavit NULL

paranoiq
Člen | 392
+
0
-

a jaký je ten případ, kdy potřebuješ tabulce nastavit jiný primární klíč než ve skutečnosti má (resp. žádný)?

semtex.989
Člen | 75
+
0
-
<?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
+
0
-

Koukám, že taky nejde zavolat metodu $table->related() 2× po sobě, podruhé vrátí FALSE

David Grudl
Nette Core | 8228
+
0
-

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
+
0
-

@David Grudl:

  1. tak o téhle fičuře nevím, na to se podívám
  2. beru to tedy tak, že primání klíč u Selection zatím nelze legitimně měnit
hrach
Člen | 1838
+
0
-
  • 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
+
0
-
  1. 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
+
0
-
  1. 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…

  2. poskytni kod – funkcionalita se měnit neměla. To, že to nefunguje stejně bude patrně bug.
  3. …řešíme primární klíč nebo něco jinéh???

Editoval hrach (25. 1. 2012 22:23)

semtex.989
Člen | 75
+
0
-

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
+
0
-
  1. jsem opravil, za chvili poslu pull
  2. 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 automaticky COUNT(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.

  1. kdy mlcky prejde preklep ve sloupci?

Editoval hrach (26. 1. 2012 20:30)

hrach
Člen | 1838
+
0
-

Uf, ale ze mi to dalo zabrat to vsechno promyslet a patricne efektivne opravit :P :D

semtex.989
Člen | 75
+
0
-

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)

  1. 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
+
0
-

ad 2) to není vůbec pravda. count() i předtím pořítala řádky, kdežto count(‚id/whatever‘) dělala doraz, v tom kodu je to jasne videt, navic metoda count() se nemenila, menila se jen ta agregation, ktera praveze pracuje jen pri sql dotazu.

Editoval hrach (26. 1. 2012 22:56)

hrach
Člen | 1838
+
0
-
  • 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
+
0
-

Díky.
Vyzkouším a napíšu.

hrach
Člen | 1838
+
0
-

Tichost pri neexistujicim sloupci fixed.

semtex.989
Člen | 75
+
0
-

Backjoin funguje opravdu dobře.

Ale s tím mým hackem property $primary se to pokusím znovu vysvětlit:

  1. Mám nějaký fetchnutý ActiveRow (primární klíč je např. 11)
  2. Zavolám <?php $descriptions = $row->related(‚products_description‘) ?>
  3. ActiveRow zavolá metodu $selection->getReferencingTable() a předá hodnotu primárního klíče (tedy 11) a vytvoří vytvoří GroupedSelection
  4. 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)