diff --git a/database/cs/@left-menu.texy b/database/cs/@left-menu.texy
index c7b6340b33..74deae2c74 100644
--- a/database/cs/@left-menu.texy
+++ b/database/cs/@left-menu.texy
@@ -1,5 +1,6 @@
Databáze
********
+- [Úvod |core]
- [Direct SQL]
- [Explorer]
- [Reflexe |reflection]
diff --git a/database/cs/core.texy b/database/cs/core.texy
index 81897def80..5516c71e27 100644
--- a/database/cs/core.texy
+++ b/database/cs/core.texy
@@ -1,551 +1,155 @@
-Direct SQL
+Nette Database
**********
.[perex]
-S Nette Database můžete pracovat dvěma způsoby - buď přímo psát SQL dotazy (Direct přístup), nebo nechat SQL generovat automaticky ([Explorer přístup|explorer]). Direct přístup vám pomůže s bezpečným sestavováním dotazů, ale zachovává vám plnou kontrolu nad jejich podobou.
+Nette Database je výkonná a elegantní databázová vrstva pro PHP, která vyniká svou jednoduchostí použití a chytrými funkcemi. Nevyžaduje žádnou složitou konfiguraci nebo generování entit, s Nette Database můžete začít pracovat okamžitě.
+S Nette Database můžete pracovat dvěma způsoby - buď psaním SQL dotazů (Direct přístup), nebo nechat SQL generovat automaticky (Explorer přístup).
-Základní použití:
-
-```php
-$database = new Nette\Database\Connection(
- 'mysql:host=127.0.0.1;dbname=test',
- 'user',
- 'password'
-);
-
-$result = $database->query('SELECT * FROM users WHERE name = ?', 'John');
-```
-
-.[note]
-Informace o vytvoření připojení a konfiguraci najdete na [samostatné stránce |configuration].
-
-
-Pokládání SQL dotazů
-====================
-
-Pro dotazování do databáze slouží metoda `query()`. Ta vrací objekt [ResultSet |api:Nette\Database\ResultSet], který reprezentuje výsledek dotazu. V případě selhání metoda [vyhodí výjimku|#Výjimky].
-
-
-Získávání dat (SELECT)
-----------------------
-
-Nejjednodušší použití je zavolat `query()` a následně výsledek dotazu, který se vrací jako objekt `ResultSet`, procházet pomocí cyklu `foreach`:
-
-```php
-$result = $database->query('SELECT * FROM users');
-
-foreach ($result as $row) {
- echo $row->id;
- echo $row->name;
-}
-```
-
-Pro bezpečné vkládání hodnot do SQL dotazů používáme parametrizované dotazy. Nette Database je dělá maximálně jednoduché - stačí za SQL dotaz přidat čárku a hodnotu:
-
-```php
-$database->query('SELECT * FROM users WHERE name = ?', $name);
-```
-
-Při více parametrech máte dvě možnosti zápisu. Buď můžete SQL dotaz "prokládat" parametry:
-
-```php
-$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age);
-```
-
-Nebo napsat nejdříve celý SQL dotaz a pak připojit všechny parametry:
-
-```php
-$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age);
-```
-
-Podívejte se, jaké techniky nabízí Nette Database pro [snadný zápis pokročilejších SQL dotazů |#Techniky dotazování].
-
-
-Ochrana před SQL injection
---------------------------
-
-Proč je důležité používat parametrizované dotazy? Protože vás chrání před útokem zvaným SQL injection, při kterém by útočník mohl podstrčit vlastní SQL příkazy a tím získat nebo poškodit data v databázi.
+
+
-.[warning]
-**Nikdy nevkládejte proměnné přímo do SQL dotazu!** Vždy používejte parametrizované dotazy, které vás ochrání před SQL injection.
+[Direct SQL]
+==========
+- Bezpečné parametrizované dotazy
+- Přesná kontrola nad podobou SQL dotazů
+- Když píšete komplexní dotazy s pokročilými funkcemi
+- Optimalizujete výkon pomocí specifických SQL funkcí
-```php
-// ❌ NEBEZPEČNÝ KÓD - zranitelný vůči SQL injection
-$database->query("SELECT * FROM users WHERE name = '$name'");
-
-// ✅ Bezpečný parametrizovaný dotaz
-$database->query('SELECT * FROM users WHERE name = ?', $name);
-```
+
-Seznamte se s [možnými bezpečnostními riziky |security].
+
-Vkládání dat (INSERT)
----------------------
+[Explorer]
+========
+- Vyvíjíte rychle bez psaní SQL
+- Intuitivní práce s relacemi mezi tabulkami
+- Oceníte automatickou optimalizaci dotazů
+- Vhodné pro rychlou a pohodlnout práci s databází
-Pro vkládání záznamů se používá SQL příkaz `INSERT`.
-
-```php
-$values = [
- 'name' => 'John Doe',
- 'email' => 'john@example.com',
-];
-$database->query('INSERT INTO users ?', $values);
-$userId = $database->getInsertId();
-```
+
-Metoda `getInsertId()` vrátí ID naposledy vloženého řádku. U některých databází (např. PostgreSQL) je nutné jako parametr specifikovat název sekvence, ze které se má ID generovat pomocí `$database->getInsertId($sequenceId)`.
+
-Jako parametry můžeme předávat i [#speciální hodnoty] jako soubory, objekty DateTime nebo výčtové typy.
-
-Vložení více záznamů najednou:
-
-```php
-$database->query('INSERT INTO users ?', [
- ['name' => 'User 1', 'email' => 'user1@mail.com'],
- ['name' => 'User 2', 'email' => 'user2@mail.com'],
-]);
-```
-Vícenásobný INSERT je mnohem rychlejší, protože se provede jediný databázový dotaz, namísto mnoha jednotlivých.
-**Bezpečnostní upozornění:** Nikdy nepoužívejte jako `$values` nevalidovaná data. Seznamte se s [možnými riziky |security#Klíče polí nejsou bezpečné API].
-
-
-Aktualizace dat (UPDATE)
-------------------------
+Instalace
+=========
-Pro aktualizacizáznamů se používá SQL příkaz `UPDATE`.
+Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]:
-```php
-// Aktualizace jednoho záznamu
-$values = [
- 'name' => 'John Smith',
-];
-$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);
+```shell .[dark]
+composer require nette/database
```
-Počet ovlivněných řádků vrátí `$result->getRowCount()`.
-
-Pro UPDATE můžeme využít operátorů `+=` a `-=`:
-```php
-$database->query('UPDATE users SET ? WHERE id = ?', [
- 'login_count+=' => 1, // inkrementace login_count
-], 1);
-```
+Připojení a konfigurace
+=======================
-Příklad vložení, nebo úpravy záznamu, pokud již existuje. Použijeme techniku `ON DUPLICATE KEY UPDATE`:
+Základní použití:
```php
-$values = [
- 'name' => $name,
- 'year' => $year,
-];
-$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?',
- $values + ['id' => $id],
- $values,
+$database = new Nette\Database\Connection(
+ 'mysql:host=127.0.0.1;dbname=test',
+ 'user',
+ 'password'
);
-// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978)
-// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978
-```
-
-Všimněte si, že Nette Database pozná, v jakém kontextu SQL příkazu parametr s polem vkládáme a podle toho z něj sestaví SQL kód. Takže z prvního pole sestavil `(id, name, year) VALUES (123, 'Jim', 1978)`, zatímco druhé převedl do podoby `name = 'Jim', year = 1978`. Podroběji se tomu věnujeme v části [Hinty pro sestavování SQL|#Hinty pro sestavování SQL].
-
-
-Mazání dat (DELETE)
--------------------
-
-Pro mazání záznamů se používá SQL příkaz `DELETE`. Příklad se získáním počtu smazaných řádků:
-
-```php
-$count = $database->query('DELETE FROM users WHERE id = ?', 1)
- ->getRowCount();
-```
-
-
-Získání dat
-===========
-
-
-Zkratky pro SELECT dotazy
--------------------------
-
-Pro zjednodušení načítání dat nabízí `Connection` několik zkratek, které kombinují volání `query()` s následujícím `fetch*()`. Tyto metody přijímají stejné parametry jako `query()`, tedy SQL dotaz a volitelné parametry.
-Plnohodnotný popis metod `fetch*()` najdete [níže|#fetch()].
-
-| `fetch($sql, ...$params): ?Row` | Provede dotaz a vrátí první řádek jako objekt `Row`
-| `fetchAll($sql, ...$params): array` | Provede dotaz a vrátí všechny řádky jako pole objektů `Row`
-| `fetchPairs($sql, ...$params): array` | Provede dotaz a vrátí asocitivní pole, kde první sloupec představuje klíč a druhý hodnotu
-| `fetchField($sql, ...$params): mixed` | Provede dotaz a vrátí hodnotu prvního políčka z prvního řádku
-| `fetchList($sql, ...$params): ?array` | Provede dotaz a vrací první řádek jako indexované pole
-
-Příklad:
-
-```php
-// fetchField() - vrátí hodnotu první buňky
-$count = $database->query('SELECT COUNT(*) FROM articles')
- ->fetchField();
-```
-
-`foreach` - iterace přes řádky
-------------------------------
-
-Po vykonání dotazu se vrací objekt [ResultSet|api:Nette\Database\ResultSet], který umožňuje procházet výsledky několika způsoby.
-Nejsnazší způsob, jak vykonat dotaz a získat řádky, je iterováním v cyklu `foreach`. Tento způsob je paměťově nejúspornější, neboť vrací data postupně a neukládá si je do paměti najednou.
-
-```php
-$result = $database->query('SELECT * FROM users');
-
-foreach ($result as $row) {
- echo $row->id;
- echo $row->name;
- // ...
-}
+$result = $database->query('SELECT * FROM users WHERE name = ?', 'John');
```
.[note]
-`ResultSet` lze iterovat pouze jednou. Pokud potřebujete iterovat opakovaně, musíte nejprve načíst data do pole, například pomocí metody `fetchAll()`.
-
-
-fetch(): ?Row .[method]
------------------------
-
-Vrací řádek jako objekt `Row`. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek.
-
-```php
-$result = $database->query('SELECT * FROM users');
-$row = $result->fetch(); // načte první řádek
-if ($row) {
- echo $row->name;
-}
-```
-
-
-fetchAll(): array .[method]
----------------------------
-
-Vrací všechny zbývající řádky z `ResultSetu` jako pole objektů `Row`.
-
-```php
-$result = $database->query('SELECT * FROM users');
-$rows = $result->fetchAll(); // načte všechny řádky
-foreach ($rows as $row) {
- echo $row->name;
-}
-```
-
-
-fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method]
----------------------------------------------------------------------------------------
-
-Vrátí výsledky jako asociativní pole. První argument určuje název sloupce, který se použije jako klíč v poli, druhý argument určuje název sloupce, který se použije jako hodnota:
-
-```php
-$result = $database->query('SELECT id, name FROM users');
-$names = $result->fetchPairs('id', 'name');
-// [1 => 'John Doe', 2 => 'Jane Doe', ...]
-```
-
-Pokud uvedeme pouze první parametr, bude hodnotou celý řádek, tedy objekt `Row`:
-
-```php
-$rows = $result->fetchPairs('id');
-// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...]
-```
-
-Pokud jako klíč uvedeme `null`, bude pole indexováno numericky od nuly:
-
-```php
-$names = $result->fetchPairs(null, 'name');
-// [0 => 'John Doe', 1 => 'Jane Doe', ...]
-```
-
-
-fetchPairs(Closure $callback): array .[method]
-----------------------------------------------
-
-Alternativně můžete jako parametr uvést callback, který bude pro každý řádek vracet buď samotnou hodnotu, nebo dvojici klíč-hodnota.
-
-```php
-$result = $database->query('SELECT * FROM users');
-$items = $result->fetchPairs(fn($row) => "$row->id - $row->name");
-// ['1 - John', '2 - Jane', ...]
-
-// Callback může také vracet pole s dvojicí klíč & hodnota:
-$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
-// ['John' => 46, 'Jane' => 21, ...]
-```
-
-
-fetchField(): mixed .[method]
------------------------------
-
-Vrací hodnotu prvního políčka z aktuálního řádku. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek.
-
-```php
-$result = $database->query('SELECT name FROM users');
-$name = $result->fetchField(); // načte jméno z prvního řádku
-```
-
-
-fetchList(): ?array .[method]
------------------------------
-
-Vrací řádek jako indexované pole. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek.
-
-```php
-$result = $database->query('SELECT name, email FROM users');
-$row = $result->fetchList(); // ['John', 'john@example.com']
-```
-
-
-getRowCount(): ?int .[method]
------------------------------
-
-Vrací počet ovlivněných řádků posledním dotazem `UPDATE` nebo `DELETE`. Pro `SELECT` je to počet vrácených řádků, ale ten nemusí být znám - v takovém případě metoda vrátí `null`.
-
-
-getColumnCount(): ?int .[method]
---------------------------------
-
-Vrací počet sloupců v `ResultSetu`.
-
-
-Konverze typů
-=============
-
-Nette Database automaticky konvertuje hodnoty vrácené z databáze na odpovídající PHP typy.
-
-
-Datum a čas
------------
-
-Časové údaje jsou převáděny na objekty `Nette\Utils\DateTime`. Pokud chcete, aby byly časové údaje převáděny na immutable objekty `Nette\Database\DateTime`, nastavte v [konfiguraci|configuration] volbu `newDateTime` na true.
-
-```php
-$row = $database->fetch('SELECT created_at FROM articles');
-echo $row->created_at instanceof DateTime; // true
-echo $row->created_at->format('j. n. Y');
-```
-
-V případě MySQL převádí datový typ `TIME` na objekty `DateInterval`.
-
-
-Booleovské hodnoty
-------------------
-
-Booleovské hodnoty jsou automaticky převedeny na `true` nebo `false`. U MySQL se převádí `TINYINT(1)` pokud nastavíme v [konfiguraci|configuration] `convertBoolean`.
-
-```php
-$row = $database->fetch('SELECT is_published FROM articles');
-echo gettype($row->is_published); // 'boolean'
-```
-
-
-Číselné hodnoty
----------------
-
-Číselné hodnoty jsou převedeny na `int` nebo `float` podle typu sloupce v databázi:
-
-```php
-$row = $database->fetch('SELECT id, price FROM products');
-echo gettype($row->id); // integer
-echo gettype($row->price); // float
-```
-
-
-Vlastní normalizace
--------------------
-
-Pomocí metody `setRowNormalizer(?callable $normalizer)` můžete nastavit vlastní funkci pro transformaci řádků z databáze. To se hodí například pro automatický převod datových typů.
-
-```php
-$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array {
- // konverze typů
- return $row;
-});
-```
-
-
-Techniky dotazování
-===================
-
-Nette Database nabízí elegantní a expresivní způsoby, jak sestavovat SQL dotazy. Podívejte se na ně.
-
-
-Podmínky WHERE
---------------
+Informace o vytvoření připojení a konfiguraci najdete na [samostatné stránce |configuration].
-Podmínky WHERE můžete zapsat jako asociativní pole, kde klíče jsou názvy sloupců a hodnoty jsou data pro porovnání. Nette Database automaticky vybere nejvhodnější SQL operátor podle typu hodnoty.
-```php
-$database->query('SELECT * FROM users WHERE', [
- 'name' => 'John',
- 'active' => true,
-]);
-// WHERE `name` = 'John' AND `active` = 1
-```
+Dva přístupy k databázi
+===========
-V klíči můžete také explicitně specifikovat operátor pro porovnání:
+S Nette Database můžete pracovat dvěma způsoby - buď psaním SQL dotazů (Direct přístup), nebo nechat SQL generovat automaticky (Explorer přístup).
-```php
-$database->query('SELECT * FROM users WHERE', [
- 'age >' => 25, // použije operátor >
- 'name LIKE' => '%John%', // použije operátor LIKE
- 'email NOT LIKE' => '%example.com%', // použije operátor NOT LIKE
-]);
-// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%'
-```
-
-Nette automaticky ošetřuje speciální případy jako `null` hodnoty nebo pole.
+[Direct přístup|direct-sql] - SQL dotazy
```php
-$database->query('SELECT * FROM products WHERE', [
- 'name' => 'Laptop', // použije operátor =
- 'category_id' => [1, 2, 3], // použije IN
- 'description' => null, // použije IS NULL
-]);
-// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL
+// SELECT s parametrizovaným dotazem
+$result = $database->query('SELECT * FROM users WHERE role = ?', 'admin');
-$database->query('SELECT * FROM products WHERE', [
- 'name NOT' => 'Laptop', // použije operátor <>
- 'category_id NOT' => [1, 2, 3], // použije NOT IN
- 'description NOT' => null, // použije IS NOT NULL
- 'id' => [], // vynechá se
+// INSERT pomocí pole hodnot
+$database->query('INSERT INTO users', [
+ 'name' => 'John',
+ 'email' => 'john@example.com',
+ 'created_at' => new DateTime,
]);
-// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL
-```
-Pro spojování podmínek se používá operátor `AND`. To lze změnit pomocí zástupného symbolu `?or` (viz níže).
-
-```php
-$database->query('SELECT * FROM users WHERE ?or', [
- 'name' => 'John',
- 'active' => true,
-]);
-// WHERE `name` = 'John' OR `active` = 1
+// UPDATE s podmínkou
+$database->query('UPDATE users SET', [
+ 'active' => true,
+ 'last_login' => new DateTime,
+], 'WHERE id = ?', $userId);
```
+Direct přístup nabízí pomocné nástroje pro bezpečné sestavování dotazů, ale zachovává vám plnou kontrolu nad jejich podobou.
-Pravidla ORDER BY
------------------
-
-Řazení `ORDER BY` se dá zapsat pomocí pole. V klíčích uvedeme sloupce a hodnotou bude boolean určující, zda řadit vzestupně:
+[Explorer přístup|explorer] - automatické generování SQL
```php
-$database->query('SELECT id FROM author ORDER BY', [
- 'id' => true, // vzestupně
- 'name' => false, // sestupně
-]);
-// SELECT id FROM author ORDER BY `id`, `name` DESC
-```
-
-
-Hinty pro sestavování SQL
--------------------------
+// Práce s tabulkou
+$users = $database->table('users');
-Hint je speciální zástupný symbol v SQL dotazu, který říká, jak se má hodnota parametru přepsat do SQL výrazu:
+// SELECT s podmínkou
+$admins = $users->where('role', 'admin');
-| Hint | Popis | Automaticky se použije
-|-----------|-------------------------------------------------|-----------------------------
-| `?name` | použije pro vložení názvu tabulky nebo sloupce | -
-| `?values` | vygeneruje `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?`
-| `?set` | vygeneruje přiřazení `key = value, ...` | `SET ?`, `KEY UPDATE ?`
-| `?and` | spojí podmínky v poli operátorem `AND` | `WHERE ?`, `HAVING ?`
-| `?or` | spojí podmínky v poli operátorem `OR` | -
-| `?order` | vygeneruje klauzuli `ORDER BY` | `ORDER BY ?`, `GROUP BY ?`
+// Práce se vztahy
+foreach ($users as $user) {
+ echo $user->name;
+ echo $user->role->description; // automatický JOIN
-Pro dynamické vkládání názvů tabulek a sloupců do dotazu slouží zástupný symbol `?name`. Nette Database se postará o správné ošetření identifikátorů podle konvencí dané databáze (např. uzavření do zpětných uvozovek v MySQL).
-
-```php
-$table = 'users';
-$column = 'name';
-$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table);
-// SELECT `name` FROM `users` WHERE id = 1 (v MySQL)
-```
-
-**Upozornění:** symbol `?name` používejte pouze pro názvy tabulek a sloupců z validovaných vstupů, jinak se vystavujete [bezpečnostnímu riziku |security#Dynamické identifikátory].
-
-Ostatní hinty obvykle není potřeba uvádět, neboť Nette používá při skládání SQL dotazu chytrou autodetekci (viz třetí sloupec tabulky). Ale můžete jej využít například v situaci, kdy chcete spojit podmínky pomocí `OR` namísto `AND`:
-
-```php
-$database->query('SELECT * FROM users WHERE ?or', [
- 'name' => 'John',
- 'email' => 'john@example.com',
-]);
-// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com'
+ // Výpis článků uživatele
+ foreach ($user->related('articles') as $article) {
+ echo $article->title;
+ }
+}
```
+Explorer přístup generuje a optimalizuje SQL dotazy automaticky. Stará se o efektivní načítání dat a práci se vztahy mezi tabulkami.
-Speciální hodnoty
------------------
-
-Kromě běžných skalárních typů (string, int, bool) můžete jako parametry předávat i speciální hodnoty:
+Oba přístupy lze v aplikaci libovolně kombinovat podle potřeby.
-- soubory: `fopen('image.gif', 'r')` vloží binární obsah souboru
-- datum a čas: objekty `DateTime` se převedou na databázový formát
-- výčtové typy: instance `enum` se převedou na jejich hodnotu
-- SQL literály: vytvořené pomocí `Connection::literal('NOW()')` se vloží přímo do dotazu
-```php
-$database->query('INSERT INTO articles ?', [
- 'title' => 'My Article',
- 'published_at' => new DateTime,
- 'content' => fopen('image.png', 'r'),
- 'state' => Status::Draft,
-]);
-```
+Podporované databáze
+===========
-U databází, které nemají nativní podporu pro datový typ `datetime` (jako SQLite a Oracle), se `DateTime` převádí na hodnotu určenou v [konfiguraci databáze|configuration] položkou `formatDateTime` (výchozí hodnota je `U` - unix timestamp).
+Nette Database podporuje následující databáze:
+|* Databázový server |* DSN jméno |* Podpora v Explorer
+| MySQL (>= 5.1) | mysql | ANO
+| PostgreSQL (>= 9.0) | pgsql | ANO
+| Sqlite 3 (>= 3.8) | sqlite | ANO
+| Oracle | oci | -
+| MS SQL (PDO_SQLSRV) | sqlsrv | ANO
+| MS SQL (PDO_DBLIB) | mssql | -
+| ODBC | odbc | -
-SQL literály
-------------
-V některých případech potřebujete jako hodnotu uvést přímo SQL kód, který se ale nemá chápat jako řetězec a escapovat. K tomuto slouží objekty třídy `Nette\Database\SqlLiteral`. Vytváří je metoda `Connection::literal()`.
-```php
-$result = $database->query('SELECT * FROM users WHERE', [
- 'name' => $name,
- 'year >' => $database::literal('YEAR()'),
-]);
-// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR())
-```
+Správa připojení
+================
-Nebo alternativě:
+Při vytvoření objektu `Connection` dojde automnaticky k připojení. Pokud chcete připojení odložit, použijte lazy režim - ten zapnete v [konfiguracI|configuration] nastavením `lazy`, nebo takto:
```php
-$result = $database->query('SELECT * FROM users WHERE', [
- 'name' => $name,
- $database::literal('year > YEAR()'),
-]);
-// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR())
+$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]);
```
-SQL literály mohou obsahovat parametry:
-
-```php
-$result = $database->query('SELECT * FROM users WHERE', [
- 'name' => $name,
- $database::literal('year > ? AND year < ?', $min, $max),
-]);
-// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017)
-```
+Pro správu připojení k databázi slouží metody `connect()`, `disconnect()` a `reconnect()`. Metoda `connect()` naváže spojení s databází, pokud ještě není navázáno. Může vyhodit výjimku `Nette\Database\ConnectionException`. Metoda `disconnect()` odpojí se od databáze. Metoda `reconnect()` odpojí se a znovu připojí k databázi. Může vyhodit výjimku `Nette\Database\ConnectionException`.
-Díky čemuž můžeme vytvářet zajímavé kombinace:
+Kromě toho můžete sledovat události spojené s připojením pomocí události `onConnect`, což je pole callbacků, které se zavolají po navázání spojení s databází.
```php
-$result = $database->query('SELECT * FROM users WHERE', [
- 'name' => $name,
- $database::literal('?or', [
- 'active' => true,
- 'role' => $role,
- ]),
-]);
-// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin')
+// proběhne po připojení k databázi
+$database->onConnect[] = function($database) {
+ echo "Připojeno k databázi";
+};
```
@@ -653,60 +257,75 @@ try {
```
-Správa připojení
-================
+Konverze typů
+=============
-Při vytvoření objektu `Connection` dojde automnaticky k připojení. Pokud chcete připojení odložit, použijte lazy režim - ten zapnete v [konfiguracI|configuration] nastavením `lazy`, nebo takto:
+Nette Database automaticky konvertuje hodnoty vrácené z databáze na odpovídající PHP typy.
-```php
-$database = new Nette\Database\Connection($dsn, $user, $password, ['lazy' => true]);
-```
-Pro správu připojení k databázi slouží metody `connect()`, `disconnect()` a `reconnect()`. Metoda `connect()` naváže spojení s databází, pokud ještě není navázáno. Může vyhodit výjimku `Nette\Database\ConnectionException`. Metoda `disconnect()` odpojí se od databáze. Metoda `reconnect()` odpojí se a znovu připojí k databázi. Může vyhodit výjimku `Nette\Database\ConnectionException`.
+Datum a čas
+-----------
-Kromě toho můžete sledovat události spojené s připojením pomocí události `onConnect`, což je pole callbacků, které se zavolají po navázání spojení s databází.
+Časové údaje jsou převáděny na objekty `Nette\Utils\DateTime`. Pokud chcete, aby byly časové údaje převáděny na immutable objekty `Nette\Database\DateTime`, nastavte v [konfiguraci|configuration] volbu `newDateTime` na true.
```php
-// proběhne po připojení k databázi
-$database->onConnect[] = function($database) {
- echo "Připojeno k databázi";
-};
+$row = $database->fetch('SELECT created_at FROM articles');
+echo $row->created_at instanceof DateTime; // true
+echo $row->created_at->format('j. n. Y');
```
+V případě MySQL převádí datový typ `TIME` na objekty `DateInterval`.
-Ladění a výkon
-==============
-Nette Database poskytuje několik užitečných nástrojů pro ladění a optimalizaci výkonu.
+Booleovské hodnoty
+------------------
+Booleovské hodnoty jsou automaticky převedeny na `true` nebo `false`. U MySQL se převádí `TINYINT(1)` pokud nastavíme v [konfiguraci|configuration] `convertBoolean`.
-Tracy Debug Bar
+```php
+$row = $database->fetch('SELECT is_published FROM articles');
+echo gettype($row->is_published); // 'boolean'
+```
+
+
+Číselné hodnoty
---------------
-Pokud používáte [Tracy |tracy:], aktivuje se automaticky panel Database v Debug baru, který zobrazuje všechny provedené dotazy, jejich parametry, dobu vykonání a místo v kódu, kde byly zavolány.
+Číselné hodnoty jsou převedeny na `int` nebo `float` podle typu sloupce v databázi:
-[* db-panel.webp *]
+```php
+$row = $database->fetch('SELECT id, price FROM products');
+echo gettype($row->id); // integer
+echo gettype($row->price); // float
+```
-Informace o dotazu
-------------------
+Vlastní normalizace
+-------------------
-Pro ladicí účely můžeme získat informace o posledním provedeném dotazu:
+Pomocí metody `setRowNormalizer(?callable $normalizer)` můžete nastavit vlastní funkci pro transformaci řádků z databáze. To se hodí například pro automatický převod datových typů.
```php
-$result = $database->query('SELECT * FROM articles');
-
-echo $database->getLastQueryString(); // vypíše SQL dotaz
-echo $result->getQueryString(); // vypíše SQL dotaz
-echo $result->getTime(); // vypíše dobu vykonání v sekundách
+$database->setRowNormalizer(function(array $row, ResultSet $resultSet): array {
+ // konverze typů
+ return $row;
+});
```
-Pro zobrazení výsledku jako HTML tabulky lze použít:
-```php
-$result = $database->query('SELECT * FROM articles');
-$result->dump();
-```
+
+Ladění a výkon
+==============
+
+Nette Database poskytuje několik užitečných nástrojů pro ladění a optimalizaci výkonu.
+
+
+Tracy Debug Bar
+---------------
+
+Pokud používáte [Tracy |tracy:], aktivuje se automaticky panel Database v Debug baru, který zobrazuje všechny provedené dotazy, jejich parametry, dobu vykonání a místo v kódu, kde byly zavolány.
+
+[* db-panel.webp *]
Logování dotazů
diff --git a/database/cs/direct-sql.texy b/database/cs/direct-sql.texy
new file mode 100644
index 0000000000..5aaeac14a0
--- /dev/null
+++ b/database/cs/direct-sql.texy
@@ -0,0 +1,501 @@
+Direct SQL
+**********
+
+.[perex]
+S Nette Database můžete pracovat dvěma způsoby - buď přímo psát SQL dotazy (Direct přístup), nebo nechat SQL generovat automaticky ([Explorer přístup|explorer]). Direct přístup vám pomůže s bezpečným sestavováním dotazů, ale zachovává vám plnou kontrolu nad jejich podobou.
+
+.[note]
+Informace o vytvoření připojení a konfiguraci najdete na [samostatné stránce |configuration].
+
+
+Pokládání SQL dotazů
+====================
+
+Pro dotazování do databáze slouží metoda `query()`. Ta vrací objekt [ResultSet |api:Nette\Database\ResultSet], který reprezentuje výsledek dotazu. V případě selhání metoda [vyhodí výjimku|#Výjimky].
+
+
+Získávání dat (SELECT)
+----------------------
+
+Nejjednodušší použití je zavolat `query()` a následně výsledek dotazu, který se vrací jako objekt `ResultSet`, procházet pomocí cyklu `foreach`:
+
+```php
+$result = $database->query('SELECT * FROM users');
+
+foreach ($result as $row) {
+ echo $row->id;
+ echo $row->name;
+}
+```
+
+Pro bezpečné vkládání hodnot do SQL dotazů používáme parametrizované dotazy. Nette Database je dělá maximálně jednoduché - stačí za SQL dotaz přidat čárku a hodnotu:
+
+```php
+$database->query('SELECT * FROM users WHERE name = ?', $name);
+```
+
+Při více parametrech máte dvě možnosti zápisu. Buď můžete SQL dotaz "prokládat" parametry:
+
+```php
+$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age);
+```
+
+Nebo napsat nejdříve celý SQL dotaz a pak připojit všechny parametry:
+
+```php
+$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age);
+```
+
+Podívejte se, jaké techniky nabízí Nette Database pro [snadný zápis pokročilejších SQL dotazů |#Techniky dotazování].
+
+
+Ochrana před SQL injection
+--------------------------
+
+Proč je důležité používat parametrizované dotazy? Protože vás chrání před útokem zvaným SQL injection, při kterém by útočník mohl podstrčit vlastní SQL příkazy a tím získat nebo poškodit data v databázi.
+
+.[warning]
+**Nikdy nevkládejte proměnné přímo do SQL dotazu!** Vždy používejte parametrizované dotazy, které vás ochrání před SQL injection.
+
+```php
+// ❌ NEBEZPEČNÝ KÓD - zranitelný vůči SQL injection
+$database->query("SELECT * FROM users WHERE name = '$name'");
+
+// ✅ Bezpečný parametrizovaný dotaz
+$database->query('SELECT * FROM users WHERE name = ?', $name);
+```
+
+Seznamte se s [možnými bezpečnostními riziky |security].
+
+
+Vkládání dat (INSERT)
+---------------------
+
+Pro vkládání záznamů se používá SQL příkaz `INSERT`.
+
+```php
+$values = [
+ 'name' => 'John Doe',
+ 'email' => 'john@example.com',
+];
+$database->query('INSERT INTO users ?', $values);
+$userId = $database->getInsertId();
+```
+
+Metoda `getInsertId()` vrátí ID naposledy vloženého řádku. U některých databází (např. PostgreSQL) je nutné jako parametr specifikovat název sekvence, ze které se má ID generovat pomocí `$database->getInsertId($sequenceId)`.
+
+Jako parametry můžeme předávat i [#speciální hodnoty] jako soubory, objekty DateTime nebo výčtové typy.
+
+Vložení více záznamů najednou:
+
+```php
+$database->query('INSERT INTO users ?', [
+ ['name' => 'User 1', 'email' => 'user1@mail.com'],
+ ['name' => 'User 2', 'email' => 'user2@mail.com'],
+]);
+```
+
+Vícenásobný INSERT je mnohem rychlejší, protože se provede jediný databázový dotaz, namísto mnoha jednotlivých.
+
+**Bezpečnostní upozornění:** Nikdy nepoužívejte jako `$values` nevalidovaná data. Seznamte se s [možnými riziky |security#Klíče polí nejsou bezpečné API].
+
+
+Aktualizace dat (UPDATE)
+------------------------
+
+Pro aktualizacizáznamů se používá SQL příkaz `UPDATE`.
+
+```php
+// Aktualizace jednoho záznamu
+$values = [
+ 'name' => 'John Smith',
+];
+$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);
+```
+
+Počet ovlivněných řádků vrátí `$result->getRowCount()`.
+
+Pro UPDATE můžeme využít operátorů `+=` a `-=`:
+
+```php
+$database->query('UPDATE users SET ? WHERE id = ?', [
+ 'login_count+=' => 1, // inkrementace login_count
+], 1);
+```
+
+Příklad vložení, nebo úpravy záznamu, pokud již existuje. Použijeme techniku `ON DUPLICATE KEY UPDATE`:
+
+```php
+$values = [
+ 'name' => $name,
+ 'year' => $year,
+];
+$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?',
+ $values + ['id' => $id],
+ $values,
+);
+// INSERT INTO users (`id`, `name`, `year`) VALUES (123, 'Jim', 1978)
+// ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978
+```
+
+Všimněte si, že Nette Database pozná, v jakém kontextu SQL příkazu parametr s polem vkládáme a podle toho z něj sestaví SQL kód. Takže z prvního pole sestavil `(id, name, year) VALUES (123, 'Jim', 1978)`, zatímco druhé převedl do podoby `name = 'Jim', year = 1978`. Podroběji se tomu věnujeme v části [Hinty pro sestavování SQL|#Hinty pro sestavování SQL].
+
+
+Mazání dat (DELETE)
+-------------------
+
+Pro mazání záznamů se používá SQL příkaz `DELETE`. Příklad se získáním počtu smazaných řádků:
+
+```php
+$count = $database->query('DELETE FROM users WHERE id = ?', 1)
+ ->getRowCount();
+```
+
+
+Získání dat
+===========
+
+
+Zkratky pro SELECT dotazy
+-------------------------
+
+Pro zjednodušení načítání dat nabízí `Connection` několik zkratek, které kombinují volání `query()` s následujícím `fetch*()`. Tyto metody přijímají stejné parametry jako `query()`, tedy SQL dotaz a volitelné parametry.
+Plnohodnotný popis metod `fetch*()` najdete [níže|#fetch()].
+
+| `fetch($sql, ...$params): ?Row` | Provede dotaz a vrátí první řádek jako objekt `Row`
+| `fetchAll($sql, ...$params): array` | Provede dotaz a vrátí všechny řádky jako pole objektů `Row`
+| `fetchPairs($sql, ...$params): array` | Provede dotaz a vrátí asocitivní pole, kde první sloupec představuje klíč a druhý hodnotu
+| `fetchField($sql, ...$params): mixed` | Provede dotaz a vrátí hodnotu prvního políčka z prvního řádku
+| `fetchList($sql, ...$params): ?array` | Provede dotaz a vrací první řádek jako indexované pole
+
+Příklad:
+
+```php
+// fetchField() - vrátí hodnotu první buňky
+$count = $database->query('SELECT COUNT(*) FROM articles')
+ ->fetchField();
+```
+
+
+`foreach` - iterace přes řádky
+------------------------------
+
+Po vykonání dotazu se vrací objekt [ResultSet|api:Nette\Database\ResultSet], který umožňuje procházet výsledky několika způsoby.
+Nejsnazší způsob, jak vykonat dotaz a získat řádky, je iterováním v cyklu `foreach`. Tento způsob je paměťově nejúspornější, neboť vrací data postupně a neukládá si je do paměti najednou.
+
+```php
+$result = $database->query('SELECT * FROM users');
+
+foreach ($result as $row) {
+ echo $row->id;
+ echo $row->name;
+ // ...
+}
+```
+
+.[note]
+`ResultSet` lze iterovat pouze jednou. Pokud potřebujete iterovat opakovaně, musíte nejprve načíst data do pole, například pomocí metody `fetchAll()`.
+
+
+fetch(): ?Row .[method]
+-----------------------
+
+Vrací řádek jako objekt `Row`. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek.
+
+```php
+$result = $database->query('SELECT * FROM users');
+$row = $result->fetch(); // načte první řádek
+if ($row) {
+ echo $row->name;
+}
+```
+
+
+fetchAll(): array .[method]
+---------------------------
+
+Vrací všechny zbývající řádky z `ResultSetu` jako pole objektů `Row`.
+
+```php
+$result = $database->query('SELECT * FROM users');
+$rows = $result->fetchAll(); // načte všechny řádky
+foreach ($rows as $row) {
+ echo $row->name;
+}
+```
+
+
+fetchPairs(string|int|null $key = null, string|int|null $value = null): array .[method]
+---------------------------------------------------------------------------------------
+
+Vrátí výsledky jako asociativní pole. První argument určuje název sloupce, který se použije jako klíč v poli, druhý argument určuje název sloupce, který se použije jako hodnota:
+
+```php
+$result = $database->query('SELECT id, name FROM users');
+$names = $result->fetchPairs('id', 'name');
+// [1 => 'John Doe', 2 => 'Jane Doe', ...]
+```
+
+Pokud uvedeme pouze první parametr, bude hodnotou celý řádek, tedy objekt `Row`:
+
+```php
+$rows = $result->fetchPairs('id');
+// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...]
+```
+
+Pokud jako klíč uvedeme `null`, bude pole indexováno numericky od nuly:
+
+```php
+$names = $result->fetchPairs(null, 'name');
+// [0 => 'John Doe', 1 => 'Jane Doe', ...]
+```
+
+
+fetchPairs(Closure $callback): array .[method]
+----------------------------------------------
+
+Alternativně můžete jako parametr uvést callback, který bude pro každý řádek vracet buď samotnou hodnotu, nebo dvojici klíč-hodnota.
+
+```php
+$result = $database->query('SELECT * FROM users');
+$items = $result->fetchPairs(fn($row) => "$row->id - $row->name");
+// ['1 - John', '2 - Jane', ...]
+
+// Callback může také vracet pole s dvojicí klíč & hodnota:
+$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
+// ['John' => 46, 'Jane' => 21, ...]
+```
+
+
+fetchField(): mixed .[method]
+-----------------------------
+
+Vrací hodnotu prvního políčka z aktuálního řádku. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek.
+
+```php
+$result = $database->query('SELECT name FROM users');
+$name = $result->fetchField(); // načte jméno z prvního řádku
+```
+
+
+fetchList(): ?array .[method]
+-----------------------------
+
+Vrací řádek jako indexované pole. Pokud už neexistují další řádky, vrací `null`. Posune interní ukazatel na další řádek.
+
+```php
+$result = $database->query('SELECT name, email FROM users');
+$row = $result->fetchList(); // ['John', 'john@example.com']
+```
+
+
+getRowCount(): ?int .[method]
+-----------------------------
+
+Vrací počet ovlivněných řádků posledním dotazem `UPDATE` nebo `DELETE`. Pro `SELECT` je to počet vrácených řádků, ale ten nemusí být znám - v takovém případě metoda vrátí `null`.
+
+
+getColumnCount(): ?int .[method]
+--------------------------------
+
+Vrací počet sloupců v `ResultSetu`.
+
+
+Informace o dotazu
+------------------
+
+Pro ladicí účely můžeme získat informace o posledním provedeném dotazu:
+
+```php
+echo $database->getLastQueryString(); // vypíše SQL dotaz
+
+$result = $database->query('SELECT * FROM articles');
+echo $result->getQueryString(); // vypíše SQL dotaz
+echo $result->getTime(); // vypíše dobu vykonání v sekundách
+```
+
+Pro zobrazení výsledku jako HTML tabulky lze použít:
+
+```php
+$result = $database->query('SELECT * FROM articles');
+$result->dump();
+```
+
+
+Techniky dotazování
+===================
+
+Nette Database nabízí elegantní a expresivní způsoby, jak sestavovat SQL dotazy. Podívejte se na ně.
+
+
+Podmínky WHERE
+--------------
+
+Podmínky WHERE můžete zapsat jako asociativní pole, kde klíče jsou názvy sloupců a hodnoty jsou data pro porovnání. Nette Database automaticky vybere nejvhodnější SQL operátor podle typu hodnoty.
+
+```php
+$database->query('SELECT * FROM users WHERE', [
+ 'name' => 'John',
+ 'active' => true,
+]);
+// WHERE `name` = 'John' AND `active` = 1
+```
+
+V klíči můžete také explicitně specifikovat operátor pro porovnání:
+
+```php
+$database->query('SELECT * FROM users WHERE', [
+ 'age >' => 25, // použije operátor >
+ 'name LIKE' => '%John%', // použije operátor LIKE
+ 'email NOT LIKE' => '%example.com%', // použije operátor NOT LIKE
+]);
+// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%'
+```
+
+Nette automaticky ošetřuje speciální případy jako `null` hodnoty nebo pole.
+
+```php
+$database->query('SELECT * FROM products WHERE', [
+ 'name' => 'Laptop', // použije operátor =
+ 'category_id' => [1, 2, 3], // použije IN
+ 'description' => null, // použije IS NULL
+]);
+// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL
+
+$database->query('SELECT * FROM products WHERE', [
+ 'name NOT' => 'Laptop', // použije operátor <>
+ 'category_id NOT' => [1, 2, 3], // použije NOT IN
+ 'description NOT' => null, // použije IS NOT NULL
+ 'id' => [], // vynechá se
+]);
+// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL
+```
+
+Pro spojování podmínek se používá operátor `AND`. To lze změnit pomocí zástupného symbolu `?or` (viz níže).
+
+```php
+$database->query('SELECT * FROM users WHERE ?or', [
+ 'name' => 'John',
+ 'active' => true,
+]);
+// WHERE `name` = 'John' OR `active` = 1
+```
+
+
+Pravidla ORDER BY
+-----------------
+
+Řazení `ORDER BY` se dá zapsat pomocí pole. V klíčích uvedeme sloupce a hodnotou bude boolean určující, zda řadit vzestupně:
+
+```php
+$database->query('SELECT id FROM author ORDER BY', [
+ 'id' => true, // vzestupně
+ 'name' => false, // sestupně
+]);
+// SELECT id FROM author ORDER BY `id`, `name` DESC
+```
+
+
+Hinty pro sestavování SQL
+-------------------------
+
+Hint je speciální zástupný symbol v SQL dotazu, který říká, jak se má hodnota parametru přepsat do SQL výrazu:
+
+| Hint | Popis | Automaticky se použije
+|-----------|-------------------------------------------------|-----------------------------
+| `?name` | použije pro vložení názvu tabulky nebo sloupce | -
+| `?values` | vygeneruje `(key, ...) VALUES (value, ...)` | `INSERT ... ?`, `REPLACE ... ?`
+| `?set` | vygeneruje přiřazení `key = value, ...` | `SET ?`, `KEY UPDATE ?`
+| `?and` | spojí podmínky v poli operátorem `AND` | `WHERE ?`, `HAVING ?`
+| `?or` | spojí podmínky v poli operátorem `OR` | -
+| `?order` | vygeneruje klauzuli `ORDER BY` | `ORDER BY ?`, `GROUP BY ?`
+
+Pro dynamické vkládání názvů tabulek a sloupců do dotazu slouží zástupný symbol `?name`. Nette Database se postará o správné ošetření identifikátorů podle konvencí dané databáze (např. uzavření do zpětných uvozovek v MySQL).
+
+```php
+$table = 'users';
+$column = 'name';
+$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table);
+// SELECT `name` FROM `users` WHERE id = 1 (v MySQL)
+```
+
+**Upozornění:** symbol `?name` používejte pouze pro názvy tabulek a sloupců z validovaných vstupů, jinak se vystavujete [bezpečnostnímu riziku |security#Dynamické identifikátory].
+
+Ostatní hinty obvykle není potřeba uvádět, neboť Nette používá při skládání SQL dotazu chytrou autodetekci (viz třetí sloupec tabulky). Ale můžete jej využít například v situaci, kdy chcete spojit podmínky pomocí `OR` namísto `AND`:
+
+```php
+$database->query('SELECT * FROM users WHERE ?or', [
+ 'name' => 'John',
+ 'email' => 'john@example.com',
+]);
+// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com'
+```
+
+
+Speciální hodnoty
+-----------------
+
+Kromě běžných skalárních typů (string, int, bool) můžete jako parametry předávat i speciální hodnoty:
+
+- soubory: `fopen('image.gif', 'r')` vloží binární obsah souboru
+- datum a čas: objekty `DateTime` se převedou na databázový formát
+- výčtové typy: instance `enum` se převedou na jejich hodnotu
+- SQL literály: vytvořené pomocí `Connection::literal('NOW()')` se vloží přímo do dotazu
+
+```php
+$database->query('INSERT INTO articles ?', [
+ 'title' => 'My Article',
+ 'published_at' => new DateTime,
+ 'content' => fopen('image.png', 'r'),
+ 'state' => Status::Draft,
+]);
+```
+
+U databází, které nemají nativní podporu pro datový typ `datetime` (jako SQLite a Oracle), se `DateTime` převádí na hodnotu určenou v [konfiguraci databáze|configuration] položkou `formatDateTime` (výchozí hodnota je `U` - unix timestamp).
+
+
+SQL literály
+------------
+
+V některých případech potřebujete jako hodnotu uvést přímo SQL kód, který se ale nemá chápat jako řetězec a escapovat. K tomuto slouží objekty třídy `Nette\Database\SqlLiteral`. Vytváří je metoda `Connection::literal()`.
+
+```php
+$result = $database->query('SELECT * FROM users WHERE', [
+ 'name' => $name,
+ 'year >' => $database::literal('YEAR()'),
+]);
+// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR())
+```
+
+Nebo alternativě:
+
+```php
+$result = $database->query('SELECT * FROM users WHERE', [
+ 'name' => $name,
+ $database::literal('year > YEAR()'),
+]);
+// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR())
+```
+
+SQL literály mohou obsahovat parametry:
+
+```php
+$result = $database->query('SELECT * FROM users WHERE', [
+ 'name' => $name,
+ $database::literal('year > ? AND year < ?', $min, $max),
+]);
+// SELECT * FROM users WHERE `name` = 'Jim' AND (year > 1978 AND year < 2017)
+```
+
+Díky čemuž můžeme vytvářet zajímavé kombinace:
+
+```php
+$result = $database->query('SELECT * FROM users WHERE', [
+ 'name' => $name,
+ $database::literal('?or', [
+ 'active' => true,
+ 'role' => $role,
+ ]),
+]);
+// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin')
+```
diff --git a/database/cs/reflection.texy b/database/cs/reflection.texy
index eb2a063055..5f95e40136 100644
--- a/database/cs/reflection.texy
+++ b/database/cs/reflection.texy
@@ -7,7 +7,7 @@ Nette Database poskytuje nástroje pro introspekci databázové struktury pomoc
Objekt reflexe získáme z instance připojení k databázi:
```php
-$reflection = $connection->getReflection();
+$reflection = $database->getReflection();
```