code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

use Nette;
use Nette\Database\Conventions\StaticConventions;

/**
 * Database context.
 */
class Context extends Nette\Object
{
	/** @var Connection */
	private $connection;

	/** @var IStructure */
	private $structure;

	/** @var IConventions */
	private $conventions;

	/** @var Nette\Caching\IStorage */
	private $cacheStorage;

	public function __construct(Connection $connection, IStructure $structure, IConventions $conventions = NULL, Nette\Caching\IStorage $cacheStorage = NULL)
	{
		$this->connection = $connection;
		$this->structure = $structure;
		$this->conventions = $conventions ?: new StaticConventions;
		$this->cacheStorage = $cacheStorage;
	}

	/** @return void */
	public function beginTransaction()
	{
		$this->connection->beginTransaction();
	}

	/** @return void */
	public function commit()
	{
		$this->connection->commit();
	}

	/** @return void */
	public function rollBack()
	{
		$this->connection->rollBack();
	}

	/**
	 * @param  string  sequence object
	 * @return string
	 */
	public function getInsertId($name = NULL)
	{
		return $this->connection->getInsertId($name);
	}

	/**
	 * Generates and executes SQL query.
	 * @param  string
	 * @param  mixed   [parameters, ...]
	 * @return ResultSet
	 */
	public function query($sql)
	{
		return $this->connection->query(func_get_args());
	}

	/**
	 * @param  string
	 * @return ResultSet
	 */
	public function queryArgs($sql, array $params)
	{
		return $this->connection->queryArgs($sql, $params);
	}

	/**
	 * @param  string
	 * @return Table\Selection
	 */
	public function table($table)
	{
		return new Table\Selection($this, $this->conventions, $table, $this->cacheStorage);
	}

	/** @return Connection */
	public function getConnection()
	{
		return $this->connection;
	}

	/** @return IStructure */
	public function getStructure()
	{
		return $this->structure;
	}

	/** @return IConventions */
	public function getConventions()
	{
		return $this->conventions;
	}

	/** @deprecated */
	public function getDatabaseReflection()
	{
		trigger_error(__METHOD__ . '() is deprecated; use getConventions() instead.', E_USER_DEPRECATED);
		return $this->conventions;
	}

	/********************* shortcuts ****************d*g**/

	/**
	 * Shortcut for query()->fetch()
	 * @param  string
	 * @param  mixed   [parameters, ...]
	 * @return Row
	 */
	public function fetch($args)
	{
		return $this->connection->query(func_get_args())->fetch();
	}

	/**
	 * Shortcut for query()->fetchField()
	 * @param  string
	 * @param  mixed   [parameters, ...]
	 * @return mixed
	 */
	public function fetchField($args)
	{
		return $this->connection->query(func_get_args())->fetchField();
	}

	/**
	 * Shortcut for query()->fetchPairs()
	 * @param  string
	 * @param  mixed   [parameters, ...]
	 * @return array
	 */
	public function fetchPairs($args)
	{
		return $this->connection->query(func_get_args())->fetchPairs();
	}

	/**
	 * Shortcut for query()->fetchAll()
	 * @param  string
	 * @param  mixed   [parameters, ...]
	 * @return array
	 */
	public function fetchAll($args)
	{
		return $this->connection->query(func_get_args())->fetchAll();
	}

	/**
	 * @return SqlLiteral
	 */
	public static function literal($value)
	{
		$args = func_get_args();
		return new SqlLiteral(array_shift($args), $args);
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Conventions;

use Nette\Database\IConventions;
use Nette\Database\IStructure;

/**
 * Conventions based on database structure.
 */
class DiscoveredConventions implements IConventions
{
	/** @var IStructure */
	protected $structure;

	public function __construct(IStructure $structure)
	{
		$this->structure = $structure;
	}

	public function getPrimary($table)
	{
		return $this->structure->getPrimaryKey($table);
	}

	public function getHasManyReference($nsTable, $key)
	{
		$candidates = $columnCandidates = array();
		$targets = $this->structure->getHasManyReference($nsTable);
		$table = preg_replace('#^(.*\.)?(.*)$#', '$2', $nsTable);

		foreach ($targets as $targetNsTable => $targetColumns) {
			$targetTable = preg_replace('#^(.*\.)?(.*)$#', '$2', $targetNsTable);
			if (stripos($targetNsTable, $key) === FALSE) {
				continue;
			}

			foreach ($targetColumns as $targetColumn) {
				if (stripos($targetColumn, $table) !== FALSE) {
					$columnCandidates[] = $candidate = array($targetNsTable, $targetColumn);
					if (strcmp($targetTable, $key) === 0 || strcmp($targetNsTable, $key) === 0) {
						return $candidate;
					}
				}

				$candidates[] = array($targetTable, array($targetNsTable, $targetColumn));
			}
		}

		if (count($columnCandidates) === 1) {
			return $columnCandidates[0];
		} elseif (count($candidates) === 1) {
			return $candidates[0][1];
		}

		foreach ($candidates as $candidate) {
			if (strtolower($candidate[0]) === strtolower($key)) {
				return $candidate[1];
			}
		}

		if (!empty($candidates)) {
			throw new AmbiguousReferenceKeyException('Ambiguous joining column in related call.');
		}

		if ($this->structure->isRebuilt()) {
			return NULL;
		}

		$this->structure->rebuild();
		return $this->getHasManyReference($nsTable, $key);
	}

	public function getBelongsToReference($table, $key)
	{
		$tableColumns = $this->structure->getBelongsToReference($table);

		foreach ($tableColumns as $column => $targetTable) {
			if (stripos($column, $key) !== FALSE) {
				return array($targetTable, $column);
			}
		}

		if ($this->structure->isRebuilt()) {
			return NULL;
		}

		$this->structure->rebuild();
		return $this->getBelongsToReference($table, $key);
	}

}

repo_name: Cerx/ToDo

license: mit


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Conventions;

use Nette\Database\IConventions;
use Nette\Object;

/**
 * Conventions based on static definition.
 */
class StaticConventions extends Object implements IConventions
{
	/** @var string */
	protected $primary;

	/** @var string */
	protected $foreign;

	/** @var string */
	protected $table;

	/**
	 * Create static conventional structure.
	 * @param  string %s stands for table name
	 * @param  string %1$s stands for key used after ->, %2$s for table name
	 * @param  string %1$s stands for key used after ->, %2$s for table name
	 */
	public function __construct($primary = 'id', $foreign = '%s_id', $table = '%s')
	{
		$this->primary = $primary;
		$this->foreign = $foreign;
		$this->table = $table;
	}

	public function getPrimary($table)
	{
		return sprintf($this->primary, $this->getColumnFromTable($table));
	}

	public function getHasManyReference($table, $key)
	{
		$table = $this->getColumnFromTable($table);
		return array(
			sprintf($this->table, $key, $table),
			sprintf($this->foreign, $table, $key),
		);
	}

	public function getBelongsToReference($table, $key)
	{
		$table = $this->getColumnFromTable($table);
		return array(
			sprintf($this->table, $key, $table),
			sprintf($this->foreign, $key, $table),
		);
	}

	protected function getColumnFromTable($name)
	{
		if ($this->table !== '%s' && preg_match('(^' . str_replace('%s', '(.*)', preg_quote($this->table)) . '\z)', $name, $match)) {
			return $match[1];
		}

		return $name;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Conventions;

/**
 * Ambiguous reference key exception.
 */
class AmbiguousReferenceKeyException extends \RuntimeException
{
}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

/**
 * Base class for all errors in the driver or SQL server.
 */
class DriverException extends \PDOException
{
	/** @var string */
	public $queryString;

	/**
	 * @return self
	 */
	public static function from(\PDOException $src)
	{
		$e = new static($src->message, NULL, $src);
		if (!$src->errorInfo && preg_match('#SQLSTATE\[(.*?)\] \[(.*?)\] (.*)#A', $src->message, $m)) {
			$m[2] = (int) $m[2];
			$e->errorInfo = array_slice($m, 1);
			$e->code = $m[1];
		} else {
			$e->errorInfo = $src->errorInfo;
			$e->code = $src->code;
		}
		return $e;
	}

	/**
	 * @return int|string|NULL  Driver-specific error code
	 */
	public function getDriverCode()
	{
		return isset($this->errorInfo[1]) ? $this->errorInfo[1] : NULL;
	}

	/**
	 * @return string|NULL  SQLSTATE error code
	 */
	public function getSqlState()
	{
		return isset($this->errorInfo[0]) ? $this->errorInfo[0] : NULL;
	}

	/**
	 * @return string|NULL  SQL command
	 */
	public function getQueryString()
	{
		return $this->queryString;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Drivers;

use Nette;

/**
 * Supplemental MS SQL database driver.
 */
class MsSqlDriver extends Nette\Object implements Nette\Database\ISupplementalDriver
{

	public function convertException(\PDOException $e)
	{
		return Nette\Database\DriverException::from($e);
	}

	/********************* SQL ****************d*g**/

	/**
	 * Delimites identifier for use in a SQL statement.
	 */
	public function delimite($name)
	{
		// @see https://msdn.microsoft.com/en-us/library/ms176027.aspx
		return '[' . str_replace(array('[', ']'), array('[[', ']]'), $name) . ']';
	}

	/**
	 * Formats boolean for use in a SQL statement.
	 */
	public function formatBool($value)
	{
		return $value ? '1' : '0';
	}

	/**
	 * Formats date-time for use in a SQL statement.
	 */
	public function formatDateTime(/*\DateTimeInterface*/ $value)
	{
		return $value->format("'Y-m-d H:i:s'");
	}

	/**
	 * Formats date-time interval for use in a SQL statement.
	 */
	public function formatDateInterval(\DateInterval $value)
	{
		throw new Nette\NotSupportedException;
	}

	/**
	 * Encodes string for use in a LIKE statement.
	 */
	public function formatLike($value, $pos)
	{
		$value = strtr($value, array("'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]'));
		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
	}

	/**
	 * Injects LIMIT/OFFSET to the SQL query.
	 */
	public function applyLimit(& $sql, $limit, $offset)
	{
		if ($offset) {
			throw new Nette\NotSupportedException('Offset is not supported by this database.');

		} elseif ($limit < 0) {
			throw new Nette\InvalidArgumentException('Negative offset or limit.');

		} elseif ($limit !== NULL) {
			$sql = preg_replace('#^\s*(SELECT(\s+DISTINCT|\s+ALL)?|UPDATE|DELETE)#i', '$0 TOP ' . (int) $limit, $sql, 1, $count);
			if (!$count) {
				throw new Nette\InvalidArgumentException('SQL query must begin with SELECT, UPDATE or DELETE command.');
			}
		}
	}

	/**
	 * Normalizes result row.
	 */
	public function normalizeRow($row)
	{
		return $row;
	}

	/********************* reflection ****************d*g**/

	/**
	 * Returns list of tables.
	 */
	public function getTables()
	{
		throw new Nette\NotImplementedException;
	}

	/**
	 * Returns metadata for all columns in a table.
	 */
	public function getColumns($table)
	{
		throw new Nette\NotImplementedException;
	}

	/**
	 * Returns metadata for all indexes in a table.
	 */
	public function getIndexes($table)
	{
		throw new Nette\NotImplementedException;
	}

	/**
	 * Returns metadata for all foreign keys in a table.
	 */
	public function getForeignKeys($table)
	{
		throw new Nette\NotImplementedException;
	}

	/**
	 * Returns associative array of detected types (IReflection::FIELD_*) in result set.
	 */
	public function getColumnTypes(\PDOStatement $statement)
	{
		return Nette\Database\Helpers::detectTypes($statement);
	}

	/**
	 * @param  string
	 * @return bool
	 */
	public function isSupported($item)
	{
		return $item === self::SUPPORT_SUBSELECT;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Drivers;

use Nette;

/**
 * Supplemental MySQL database driver.
 */
class MySqlDriver extends Nette\Object implements Nette\Database\ISupplementalDriver
{
	const ERROR_ACCESS_DENIED = 1045;
	const ERROR_DUPLICATE_ENTRY = 1062;
	const ERROR_DATA_TRUNCATED = 1265;

	/** @var Nette\Database\Connection */
	private $connection;

	/**
	 * Driver options:
	 *   - charset => character encoding to set (default is utf8 or utf8mb4 since MySQL 5.5.3)
	 *   - sqlmode => see http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html
	 */
	public function __construct(Nette\Database\Connection $connection, array $options)
	{
		$this->connection = $connection;
		$charset = isset($options['charset'])
			? $options['charset']
			: (version_compare($connection->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION), '5.5.3', '>=') ? 'utf8mb4' : 'utf8');
		if ($charset) {
			$connection->query("SET NAMES '$charset'");
		}
		if (isset($options['sqlmode'])) {
			$connection->query("SET sql_mode='$options[sqlmode]'");
		}
	}

	/**
	 * @return Nette\Database\DriverException
	 */
	public function convertException(\PDOException $e)
	{
		$code = isset($e->errorInfo[1]) ? $e->errorInfo[1] : NULL;
		if (in_array($code, array(1216, 1217, 1451, 1452, 1701), TRUE)) {
			return Nette\Database\ForeignKeyConstraintViolationException::from($e);

		} elseif (in_array($code, array(1062, 1557, 1569, 1586), TRUE)) {
			return Nette\Database\UniqueConstraintViolationException::from($e);

		} elseif ($code >= 2001 && $code <= 2028) {
			return Nette\Database\ConnectionException::from($e);

		} elseif (in_array($code, array(1048, 1121, 1138, 1171, 1252, 1263, 1566), TRUE)) {
			return Nette\Database\NotNullConstraintViolationException::from($e);

		} else {
			return Nette\Database\DriverException::from($e);
		}
	}

	/********************* SQL ****************d*g**/

	/**
	 * Delimites identifier for use in a SQL statement.
	 */
	public function delimite($name)
	{
		// @see http://dev.mysql.com/doc/refman/5.0/en/identifiers.html
		return '`' . str_replace('`', '``', $name) . '`';
	}

	/**
	 * Formats boolean for use in a SQL statement.
	 */
	public function formatBool($value)
	{
		return $value ? '1' : '0';
	}

	/**
	 * Formats date-time for use in a SQL statement.
	 */
	public function formatDateTime(/*\DateTimeInterface*/ $value)
	{
		return $value->format("'Y-m-d H:i:s'");
	}

	/**
	 * Formats date-time interval for use in a SQL statement.
	 */
	public function formatDateInterval(\DateInterval $value)
	{
		return $value->format("'%r%h:%I:%S'");
	}

	/**
	 * Encodes string for use in a LIKE statement.
	 */
	public function formatLike($value, $pos)
	{
		$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
	}

	/**
	 * Injects LIMIT/OFFSET to the SQL query.
	 */
	public function applyLimit(& $sql, $limit, $offset)
	{
		if ($limit < 0 || $offset < 0) {
			throw new Nette\InvalidArgumentException('Negative offset or limit.');

		} elseif ($limit !== NULL || $offset) {
			// see http://dev.mysql.com/doc/refman/5.0/en/select.html
			$sql .= ' LIMIT ' . ($limit === NULL ? '18446744073709551615' : (int) $limit)
				. ($offset ? ' OFFSET ' . (int) $offset : '');
		}
	}

	/**
	 * Normalizes result row.
	 */
	public function normalizeRow($row)
	{
		return $row;
	}

	/********************* reflection ****************d*g**/

	/**
	 * Returns list of tables.
	 */
	public function getTables()
	{
		$tables = array();
		foreach ($this->connection->query('SHOW FULL TABLES') as $row) {
			$tables[] = array(
				'name' => $row[0],
				'view' => isset($row[1]) && $row[1] === 'VIEW',
			);
		}
		return $tables;
	}

	/**
	 * Returns metadata for all columns in a table.
	 */
	public function getColumns($table)
	{
		$columns = array();
		foreach ($this->connection->query('SHOW FULL COLUMNS FROM ' . $this->delimite($table)) as $row) {
			$type = explode('(', $row['Type']);
			$columns[] = array(
				'name' => $row['Field'],
				'table' => $table,
				'nativetype' => strtoupper($type[0]),
				'size' => isset($type[1]) ? (int) $type[1] : NULL,
				'unsigned' => (bool) strstr($row['Type'], 'unsigned'),
				'nullable' => $row['Null'] === 'YES',
				'default' => $row['Default'],
				'autoincrement' => $row['Extra'] === 'auto_increment',
				'primary' => $row['Key'] === 'PRI',
				'vendor' => (array) $row,
			);
		}
		return $columns;
	}

	/**
	 * Returns metadata for all indexes in a table.
	 */
	public function getIndexes($table)
	{
		$indexes = array();
		foreach ($this->connection->query('SHOW INDEX FROM ' . $this->delimite($table)) as $row) {
			$indexes[$row['Key_name']]['name'] = $row['Key_name'];
			$indexes[$row['Key_name']]['unique'] = !$row['Non_unique'];
			$indexes[$row['Key_name']]['primary'] = $row['Key_name'] === 'PRIMARY';
			$indexes[$row['Key_name']]['columns'][$row['Seq_in_index'] - 1] = $row['Column_name'];
		}
		return array_values($indexes);
	}

	/**
	 * Returns metadata for all foreign keys in a table.
	 */
	public function getForeignKeys($table)
	{
		$keys = array();
		$query = 'SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE '
			. 'WHERE TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL AND TABLE_NAME = ' . $this->connection->quote($table);

		foreach ($this->connection->query($query) as $id => $row) {
			$keys[$id]['name'] = $row['CONSTRAINT_NAME']; // foreign key name
			$keys[$id]['local'] = $row['COLUMN_NAME']; // local columns
			$keys[$id]['table'] = $row['REFERENCED_TABLE_NAME']; // referenced table
			$keys[$id]['foreign'] = $row['REFERENCED_COLUMN_NAME']; // referenced columns
		}

		return array_values($keys);
	}

	/**
	 * Returns associative array of detected types (IReflection::FIELD_*) in result set.
	 */
	public function getColumnTypes(\PDOStatement $statement)
	{
		$types = array();
		$count = $statement->columnCount();
		for ($col = 0; $col < $count; $col++) {
			$meta = $statement->getColumnMeta($col);
			if (isset($meta['native_type'])) {
				$types[$meta['name']] = $type = Nette\Database\Helpers::detectType($meta['native_type']);
				if ($type === Nette\Database\IStructure::FIELD_TIME) {
					$types[$meta['name']] = Nette\Database\IStructure::FIELD_TIME_INTERVAL;
				}
			}
		}
		return $types;
	}

	/**
	 * @param  string
	 * @return bool
	 */
	public function isSupported($item)
	{
		// MULTI_COLUMN_AS_OR_COND due to mysql bugs:
		// - http://bugs.mysql.com/bug.php?id=31188
		// - http://bugs.mysql.com/bug.php?id=35819
		// and more.
		return $item === self::SUPPORT_SELECT_UNGROUPED_COLUMNS || $item === self::SUPPORT_MULTI_COLUMN_AS_OR_COND;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Drivers;

use Nette;

/**
 * Supplemental Oracle database driver.
 */
class OciDriver extends Nette\Object implements Nette\Database\ISupplementalDriver
{
	/** @var Nette\Database\Connection */
	private $connection;

	/** @var string  Datetime format */
	private $fmtDateTime;

	public function __construct(Nette\Database\Connection $connection, array $options)
	{
		$this->connection = $connection;
		$this->fmtDateTime = isset($options['formatDateTime']) ? $options['formatDateTime'] : 'U';
	}

	public function convertException(\PDOException $e)
	{
		$code = isset($e->errorInfo[1]) ? $e->errorInfo[1] : NULL;
		if (in_array($code, array(1, 2299, 38911), TRUE)) {
			return Nette\Database\UniqueConstraintViolationException::from($e);

		} elseif (in_array($code, array(1400), TRUE)) {
			return Nette\Database\NotNullConstraintViolationException::from($e);

		} elseif (in_array($code, array(2266, 2291, 2292), TRUE)) {
			return Nette\Database\ForeignKeyConstraintViolationException::from($e);

		} else {
			return Nette\Database\DriverException::from($e);
		}
	}

	/********************* SQL ****************d*g**/

	/**
	 * Delimites identifier for use in a SQL statement.
	 */
	public function delimite($name)
	{
		// @see http://download.oracle.com/docs/cd/B10500_01/server.920/a96540/sql_elements9a.htm
		return '"' . str_replace('"', '""', $name) . '"';
	}

	/**
	 * Formats boolean for use in a SQL statement.
	 */
	public function formatBool($value)
	{
		return $value ? '1' : '0';
	}

	/**
	 * Formats date-time for use in a SQL statement.
	 */
	public function formatDateTime(/*\DateTimeInterface*/ $value)
	{
		return $value->format($this->fmtDateTime);
	}

	/**
	 * Formats date-time interval for use in a SQL statement.
	 */
	public function formatDateInterval(\DateInterval $value)
	{
		throw new Nette\NotSupportedException;
	}

	/**
	 * Encodes string for use in a LIKE statement.
	 */
	public function formatLike($value, $pos)
	{
		throw new Nette\NotImplementedException;
	}

	/**
	 * Injects LIMIT/OFFSET to the SQL query.
	 */
	public function applyLimit(& $sql, $limit, $offset)
	{
		if ($limit < 0 || $offset < 0) {
			throw new Nette\InvalidArgumentException('Negative offset or limit.');

		} elseif ($offset) {
			// see http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
			$sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
				. ($limit !== NULL ? 'WHERE ROWNUM <= ' . ((int) $offset + (int) $limit) : '')
				. ') WHERE "__rnum" > '. (int) $offset;

		} elseif ($limit !== NULL) {
			$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . (int) $limit;
		}
	}

	/**
	 * Normalizes result row.
	 */
	public function normalizeRow($row)
	{
		return $row;
	}

	/********************* reflection ****************d*g**/

	/**
	 * Returns list of tables.
	 */
	public function getTables()
	{
		$tables = array();
		foreach ($this->connection->query('SELECT * FROM cat') as $row) {
			if ($row[1] === 'TABLE' || $row[1] === 'VIEW') {
				$tables[] = array(
					'name' => $row[0],
					'view' => $row[1] === 'VIEW',
				);
			}
		}
		return $tables;
	}

	/**
	 * Returns metadata for all columns in a table.
	 */
	public function getColumns($table)
	{
		throw new Nette\NotImplementedException;
	}

	/**
	 * Returns metadata for all indexes in a table.
	 */
	public function getIndexes($table)
	{
		throw new Nette\NotImplementedException;
	}

	/**
	 * Returns metadata for all foreign keys in a table.
	 */
	public function getForeignKeys($table)
	{
		throw new Nette\NotImplementedException;
	}

	/**
	 * Returns associative array of detected types (IReflection::FIELD_*) in result set.
	 */
	public function getColumnTypes(\PDOStatement $statement)
	{
		return Nette\Database\Helpers::detectTypes($statement);
	}

	/**
	 * @param  string
	 * @return bool
	 */
	public function isSupported($item)
	{
		return $item === self::SUPPORT_SEQUENCE || $item === self::SUPPORT_SUBSELECT;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Drivers;

use Nette;

/**
 * Supplemental ODBC database driver.
 */
class OdbcDriver extends Nette\Object implements Nette\Database\ISupplementalDriver
{

	public function convertException(\PDOException $e)
	{
		return Nette\Database\DriverException::from($e);
	}

	/********************* SQL ****************d*g**/

	/**
	 * Delimites identifier for use in a SQL statement.
	 */
	public function delimite($name)
	{
		return '[' . str_replace(array('[', ']'), array('[[', ']]'), $name) . ']';
	}

	/**
	 * Formats boolean for use in a SQL statement.
	 */
	public function formatBool($value)
	{
		return $value ? '1' : '0';
	}

	/**
	 * Formats date-time for use in a SQL statement.
	 */
	public function formatDateTime(/*\DateTimeInterface*/ $value)
	{
		return $value->format('#m/d/Y H:i:s#');
	}

	/**
	 * Formats date-time interval for use in a SQL statement.
	 */
	public function formatDateInterval(\DateInterval $value)
	{
		throw new Nette\NotSupportedException;
	}

	/**
	 * Encodes string for use in a LIKE statement.
	 */
	public function formatLike($value, $pos)
	{
		$value = strtr($value, array("'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]'));
		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
	}

	/**
	 * Injects LIMIT/OFFSET to the SQL query.
	 */
	public function applyLimit(& $sql, $limit, $offset)
	{
		if ($offset) {
			throw new Nette\NotSupportedException('Offset is not supported by this database.');

		} elseif ($limit < 0) {
			throw new Nette\InvalidArgumentException('Negative offset or limit.');

		} elseif ($limit !== NULL) {
			$sql = preg_replace('#^\s*(SELECT(\s+DISTINCT|\s+ALL)?|UPDATE|DELETE)#i', '$0 TOP ' . (int) $limit, $sql, 1, $count);
			if (!$count) {
				throw new Nette\InvalidArgumentException('SQL query must begin with SELECT, UPDATE or DELETE command.');
			}
		}
	}

	/**
	 * Normalizes result row.
	 */
	public function normalizeRow($row)
	{
		return $row;
	}

	/********************* reflection ****************d*g**/

	/**
	 * Returns list of tables.
	 */
	public function getTables()
	{
		throw new Nette\NotImplementedException;
	}

	/**
	 * Returns metadata for all columns in a table.
	 */
	public function getColumns($table)
	{
		throw new Nette\NotImplementedException;
	}

	/**
	 * Returns metadata for all indexes in a table.
	 */
	public function getIndexes($table)
	{
		throw new Nette\NotImplementedException;
	}

	/**
	 * Returns metadata for all foreign keys in a table.
	 */
	public function getForeignKeys($table)
	{
		throw new Nette\NotImplementedException;
	}

	/**
	 * Returns associative array of detected types (IReflection::FIELD_*) in result set.
	 */
	public function getColumnTypes(\PDOStatement $statement)
	{
		return Nette\Database\Helpers::detectTypes($statement);
	}

	/**
	 * @param  string
	 * @return bool
	 */
	public function isSupported($item)
	{
		return $item === self::SUPPORT_SUBSELECT;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Drivers;

use Nette;

/**
 * Supplemental PostgreSQL database driver.
 */
class PgSqlDriver 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;
	}

	public function convertException(\PDOException $e)
	{
		$code = isset($e->errorInfo[0]) ? $e->errorInfo[0] : NULL;
		if ($code === '0A000' && strpos($e->getMessage(), 'truncate') !== FALSE) {
			return Nette\Database\ForeignKeyConstraintViolationException::from($e);

		} elseif ($code === '23502') {
			return Nette\Database\NotNullConstraintViolationException::from($e);

		} elseif ($code === '23503') {
			return Nette\Database\ForeignKeyConstraintViolationException::from($e);

		} elseif ($code === '23505') {
			return Nette\Database\UniqueConstraintViolationException::from($e);

		} elseif ($code === '08006') {
			return Nette\Database\ConnectionException::from($e);

		} else {
			return Nette\Database\DriverException::from($e);
		}
	}

	/********************* SQL ****************d*g**/

	/**
	 * Delimites identifier for use in a SQL statement.
	 */
	public function delimite($name)
	{
		// @see http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
		return '"' . str_replace('"', '""', $name) . '"';
	}

	/**
	 * Formats boolean for use in a SQL statement.
	 */
	public function formatBool($value)
	{
		return $value ? 'TRUE' : 'FALSE';
	}

	/**
	 * Formats date-time for use in a SQL statement.
	 */
	public function formatDateTime(/*\DateTimeInterface*/ $value)
	{
		return $value->format("'Y-m-d H:i:s'");
	}

	/**
	 * Formats date-time interval for use in a SQL statement.
	 */
	public function formatDateInterval(\DateInterval $value)
	{
		throw new Nette\NotSupportedException;
	}

	/**
	 * Encodes string for use in a LIKE statement.
	 */
	public function formatLike($value, $pos)
	{
		$bs = substr($this->connection->quote('\\', \PDO::PARAM_STR), 1, -1); // standard_conforming_strings = on/off
		$value = substr($this->connection->quote($value, \PDO::PARAM_STR), 1, -1);
		$value = strtr($value, array('%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\'));
		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
	}

	/**
	 * Injects LIMIT/OFFSET to the SQL query.
	 */
	public function applyLimit(& $sql, $limit, $offset)
	{
		if ($limit < 0 || $offset < 0) {
			throw new Nette\InvalidArgumentException('Negative offset or limit.');
		}
		if ($limit !== NULL) {
			$sql .= ' LIMIT ' . (int) $limit;
		}
		if ($offset) {
			$sql .= ' OFFSET ' . (int) $offset;
		}
	}

	/**
	 * Normalizes result row.
	 */
	public function normalizeRow($row)
	{
		return $row;
	}

	/********************* reflection ****************d*g**/

	/**
	 * Returns list of tables.
	 */
	public function getTables()
	{
		$tables = array();
		foreach ($this->connection->query("
			SELECT DISTINCT ON (c.relname)
				c.relname::varchar AS name,
				c.relkind = 'v' AS view,
				n.nspname::varchar || '.' || c.relname::varchar AS \"fullName\"
			FROM
				pg_catalog.pg_class AS c
				JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace
			WHERE
				c.relkind IN ('r', 'v')
				AND n.nspname = ANY (pg_catalog.current_schemas(FALSE))
			ORDER BY
				c.relname
		") as $row) {
			$tables[] = (array) $row;
		}

		return $tables;
	}

	/**
	 * Returns metadata for all columns in a table.
	 */
	public function getColumns($table)
	{
		$columns = array();
		foreach ($this->connection->query("
			SELECT
				a.attname::varchar AS name,
				c.relname::varchar AS table,
				upper(t.typname) AS nativetype,
				CASE WHEN a.atttypmod = -1 THEN NULL ELSE a.atttypmod -4 END AS size,
				FALSE AS unsigned,
				NOT (a.attnotnull OR t.typtype = 'd' AND t.typnotnull) AS nullable,
				pg_catalog.pg_get_expr(ad.adbin, 'pg_catalog.pg_attrdef'::regclass)::varchar AS default,
				coalesce(co.contype = 'p' AND strpos(ad.adsrc, 'nextval') = 1, FALSE) AS autoincrement,
				coalesce(co.contype = 'p', FALSE) AS primary,
				substring(pg_catalog.pg_get_expr(ad.adbin, 'pg_catalog.pg_attrdef'::regclass) from 'nextval[(]''\"?([^''\"]+)') AS sequence
			FROM
				pg_catalog.pg_attribute AS a
				JOIN pg_catalog.pg_class AS c ON a.attrelid = c.oid
				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 = 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->delimiteFQN($table))}::regclass
				AND a.attnum > 0
				AND NOT a.attisdropped
			ORDER BY
				a.attnum
		") as $row) {
			$column = (array) $row;
			$column['vendor'] = $column;
			unset($column['sequence']);

			$columns[] = $column;
		}

		return $columns;
	}

	/**
	 * Returns metadata for all indexes in a table.
	 */
	public function getIndexes($table)
	{
		$indexes = array();
		foreach ($this->connection->query("
			SELECT
				c2.relname::varchar AS name,
				i.indisunique AS unique,
				i.indisprimary AS primary,
				a.attname::varchar AS column
			FROM
				pg_catalog.pg_class AS c1
				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
				c1.relkind = 'r'
				AND c1.oid = {$this->connection->quote($this->delimiteFQN($table))}::regclass
		") as $row) {
			$indexes[$row['name']]['name'] = $row['name'];
			$indexes[$row['name']]['unique'] = $row['unique'];
			$indexes[$row['name']]['primary'] = $row['primary'];
			$indexes[$row['name']]['columns'][] = $row['column'];
		}

		return array_values($indexes);
	}

	/**
	 * Returns metadata for all foreign keys in a table.
	 */
	public function getForeignKeys($table)
	{
		/* Does't work with multicolumn foreign keys */
		return $this->connection->query("
			SELECT
				co.conname::varchar AS name,
				al.attname::varchar AS local,
				nf.nspname || '.' || cf.relname::varchar AS table,
				af.attname::varchar AS foreign
			FROM
				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->delimiteFQN($table))}::regclass
				AND nf.nspname = ANY (pg_catalog.current_schemas(FALSE))
		")->fetchAll();
	}

	/**
	 * Returns associative array of detected types (IReflection::FIELD_*) in result set.
	 */
	public function getColumnTypes(\PDOStatement $statement)
	{
		return Nette\Database\Helpers::detectTypes($statement);
	}

	/**
	 * @param  string
	 * @return bool
	 */
	public function isSupported($item)
	{
		return $item === self::SUPPORT_SEQUENCE || $item === self::SUPPORT_SUBSELECT || $item === self::SUPPORT_SCHEMA;
	}

	/**
	 * Converts: schema.name => "schema"."name"
	 * @param  string
	 * @return string
	 */
	private function delimiteFQN($name)
	{
		return implode('.', array_map(array($this, 'delimite'), explode('.', $name)));
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Drivers;

use Nette;

/**
 * Supplemental SQLite2 database driver.
 */
class Sqlite2Driver extends SqliteDriver
{

	/**
	 * Encodes string for use in a LIKE statement.
	 */
	public function formatLike($value, $pos)
	{
		throw new Nette\NotSupportedException;
	}

	/**
	 * Returns metadata for all foreign keys in a table.
	 */
	public function getForeignKeys($table)
	{
		throw new Nette\NotSupportedException; // @see http://www.sqlite.org/foreignkeys.html
	}

	/**
	 * Not supported.
	 */
	public function getColumnTypes(\PDOStatement $statement)
	{
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Drivers;

use Nette;

/**
 * Supplemental SQLite3 database driver.
 */
class SqliteDriver extends Nette\Object implements Nette\Database\ISupplementalDriver
{
	/** @var Nette\Database\Connection */
	private $connection;

	/** @var string  Datetime format */
	private $fmtDateTime;

	public function __construct(Nette\Database\Connection $connection, array $options)
	{
		$this->connection = $connection;
		$this->fmtDateTime = isset($options['formatDateTime']) ? $options['formatDateTime'] : 'U';
	}

	public function convertException(\PDOException $e)
	{
		$code = isset($e->errorInfo[1]) ? $e->errorInfo[1] : NULL;
		$msg = $e->getMessage();
		if ($code !== 19) {
			return Nette\Database\DriverException::from($e);

		} elseif (strpos($msg, 'must be unique') !== FALSE
			|| strpos($msg, 'is not unique') !== FALSE
			|| strpos($msg, 'UNIQUE constraint failed') !== FALSE
		) {
			return Nette\Database\UniqueConstraintViolationException::from($e);

		} elseif (strpos($msg, 'may not be NULL') !== FALSE
			|| strpos($msg, 'NOT NULL constraint failed') !== FALSE
		) {
			return Nette\Database\NotNullConstraintViolationException::from($e);

		} elseif (strpos($msg, 'foreign key constraint failed') !== FALSE
			|| strpos($msg, 'FOREIGN KEY constraint failed') !== FALSE
		) {
			return Nette\Database\ForeignKeyConstraintViolationException::from($e);

		} else {
			return Nette\Database\ConstraintViolationException::from($e);
		}
	}

	/********************* SQL ****************d*g**/

	/**
	 * Delimites identifier for use in a SQL statement.
	 */
	public function delimite($name)
	{
		return '[' . strtr($name, '[]', '  ') . ']';
	}

	/**
	 * Formats boolean for use in a SQL statement.
	 */
	public function formatBool($value)
	{
		return $value ? '1' : '0';
	}

	/**
	 * Formats date-time for use in a SQL statement.
	 */
	public function formatDateTime(/*\DateTimeInterface*/ $value)
	{
		return $value->format($this->fmtDateTime);
	}

	/**
	 * Formats date-time interval for use in a SQL statement.
	 */
	public function formatDateInterval(\DateInterval $value)
	{
		throw new Nette\NotSupportedException;
	}

	/**
	 * Encodes string for use in a LIKE statement.
	 */
	public function formatLike($value, $pos)
	{
		$value = addcslashes(substr($this->connection->quote($value), 1, -1), '%_\\');
		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
	}

	/**
	 * Injects LIMIT/OFFSET to the SQL query.
	 */
	public function applyLimit(& $sql, $limit, $offset)
	{
		if ($limit < 0 || $offset < 0) {
			throw new Nette\InvalidArgumentException('Negative offset or limit.');

		} elseif ($limit !== NULL || $offset) {
			$sql .= ' LIMIT ' . ($limit === NULL ? '-1' : (int) $limit)
				. ($offset ? ' OFFSET ' . (int) $offset : '');
		}
	}

	/**
	 * Normalizes result row.
	 */
	public function normalizeRow($row)
	{
		foreach ($row as $key => $value) {
			unset($row[$key]);
			if ($key[0] === '[' || $key[0] === '"') {
				$key = substr($key, 1, -1);
			}
			$row[$key] = $value;
		}
		return $row;
	}

	/********************* reflection ****************d*g**/

	/**
	 * Returns list of tables.
	 */
	public function getTables()
	{
		$tables = array();
		foreach ($this->connection->query("
			SELECT name, type = 'view' as view FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%'
			UNION ALL
			SELECT name, type = 'view' as view FROM sqlite_temp_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%'
			ORDER BY name
		") as $row) {
			$tables[] = array(
				'name' => $row->name,
				'view' => (bool) $row->view,
			);
		}

		return $tables;
	}

	/**
	 * Returns metadata for all columns in a table.
	 */
	public function getColumns($table)
	{
		$meta = $this->connection->query("
			SELECT sql FROM sqlite_master WHERE type = 'table' AND name = {$this->connection->quote($table)}
			UNION ALL
			SELECT sql FROM sqlite_temp_master WHERE type = 'table' AND name = {$this->connection->quote($table)}
		")->fetch();

		$columns = array();
		foreach ($this->connection->query("PRAGMA table_info({$this->delimite($table)})") as $row) {
			$column = $row['name'];
			$pattern = "/(\"$column\"|\[$column\]|$column)\\s+[^,]+\\s+PRIMARY\\s+KEY\\s+AUTOINCREMENT/Ui";
			$type = explode('(', $row['type']);
			$columns[] = array(
				'name' => $column,
				'table' => $table,
				'nativetype' => strtoupper($type[0]),
				'size' => isset($type[1]) ? (int) $type[1] : NULL,
				'unsigned' => FALSE,
				'nullable' => $row['notnull'] == '0',
				'default' => $row['dflt_value'],
				'autoincrement' => (bool) preg_match($pattern, $meta['sql']),
				'primary' => $row['pk'] > 0,
				'vendor' => (array) $row,
			);
		}
		return $columns;
	}

	/**
	 * Returns metadata for all indexes in a table.
	 */
	public function getIndexes($table)
	{
		$indexes = array();
		foreach ($this->connection->query("PRAGMA index_list({$this->delimite($table)})") as $row) {
			$indexes[$row['name']]['name'] = $row['name'];
			$indexes[$row['name']]['unique'] = (bool) $row['unique'];
			$indexes[$row['name']]['primary'] = FALSE;
		}

		foreach ($indexes as $index => $values) {
			$res = $this->connection->query("PRAGMA index_info({$this->delimite($index)})");
			while ($row = $res->fetch(TRUE)) {
				$indexes[$index]['columns'][$row['seqno']] = $row['name'];
			}
		}

		$columns = $this->getColumns($table);
		foreach ($indexes as $index => $values) {
			$column = $indexes[$index]['columns'][0];
			foreach ($columns as $info) {
				if ($column == $info['name']) {
					$indexes[$index]['primary'] = (bool) $info['primary'];
					break;
				}
			}
		}
		if (!$indexes) { // @see http://www.sqlite.org/lang_createtable.html#rowid
			foreach ($columns as $column) {
				if ($column['vendor']['pk']) {
					$indexes[] = array(
						'name' => 'ROWID',
						'unique' => TRUE,
						'primary' => TRUE,
						'columns' => array($column['name']),
					);
					break;
				}
			}
		}

		return array_values($indexes);
	}

	/**
	 * Returns metadata for all foreign keys in a table.
	 */
	public function getForeignKeys($table)
	{
		$keys = array();
		foreach ($this->connection->query("PRAGMA foreign_key_list({$this->delimite($table)})") as $row) {
			$keys[$row['id']]['name'] = $row['id']; // foreign key name
			$keys[$row['id']]['local'] = $row['from']; // local columns
			$keys[$row['id']]['table'] = $row['table']; // referenced table
			$keys[$row['id']]['foreign'] = $row['to']; // referenced columns
			$keys[$row['id']]['onDelete'] = $row['on_delete'];
			$keys[$row['id']]['onUpdate'] = $row['on_update'];

			if ($keys[$row['id']]['foreign'][0] == NULL) {
				$keys[$row['id']]['foreign'] = NULL;
			}
		}
		return array_values($keys);
	}

	/**
	 * Returns associative array of detected types (IReflection::FIELD_*) in result set.
	 */
	public function getColumnTypes(\PDOStatement $statement)
	{
		$types = array();
		$count = $statement->columnCount();
		for ($col = 0; $col < $count; $col++) {
			$meta = $statement->getColumnMeta($col);
			if (isset($meta['sqlite:decl_type'])) {
				if ($meta['sqlite:decl_type'] === 'DATE') {
					$types[$meta['name']] = Nette\Database\IStructure::FIELD_UNIX_TIMESTAMP;
				} else {
					$types[$meta['name']] = Nette\Database\Helpers::detectType($meta['sqlite:decl_type']);
				}
			} elseif (isset($meta['native_type'])) {
				$types[$meta['name']] = Nette\Database\Helpers::detectType($meta['native_type']);
			}
		}
		return $types;
	}

	/**
	 * @param  string
	 * @return bool
	 */
	public function isSupported($item)
	{
		return $item === self::SUPPORT_MULTI_INSERT_AS_SELECT || $item === self::SUPPORT_SUBSELECT || $item === self::SUPPORT_MULTI_COLUMN_AS_OR_COND;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Drivers;

use Nette;

/**
 * Supplemental SQL Server 2005 and later database driver.
 */
class SqlsrvDriver extends Nette\Object implements Nette\Database\ISupplementalDriver
{
	/** @var Nette\Database\Connection */
	private $connection;

	/** @var string */
	private $version;

	public function __construct(Nette\Database\Connection $connection, array $options)
	{
		$this->connection = $connection;
		$this->version = $connection->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
	}

	public function convertException(\PDOException $e)
	{
		return Nette\Database\DriverException::from($e);
	}

	/********************* SQL ****************d*g**/

	/**
	 * Delimites identifier for use in a SQL statement.
	 */
	public function delimite($name)
	{
		/** @see https://msdn.microsoft.com/en-us/library/ms176027.aspx */
		return '[' . str_replace(']', ']]', $name) . ']';
	}

	/**
	 * Formats boolean for use in a SQL statement.
	 */
	public function formatBool($value)
	{
		return $value ? '1' : '0';
	}

	/**
	 * Formats date-time for use in a SQL statement.
	 */
	public function formatDateTime(/*\DateTimeInterface*/ $value)
	{
		/** @see https://msdn.microsoft.com/en-us/library/ms187819.aspx */
		return $value->format("'Y-m-d H:i:s'");
	}

	/**
	 * Formats date-time interval for use in a SQL statement.
	 */
	public function formatDateInterval(\DateInterval $value)
	{
		throw new Nette\NotSupportedException;
	}

	/**
	 * Encodes string for use in a LIKE statement.
	 */
	public function formatLike($value, $pos)
	{
		/** @see https://msdn.microsoft.com/en-us/library/ms179859.aspx */
		$value = strtr($value, array("'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]'));
		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
	}

	/**
	 * Injects LIMIT/OFFSET to the SQL query.
	 */
	public function applyLimit(& $sql, $limit, $offset)
	{
		if ($limit < 0 || $offset < 0) {
			throw new Nette\InvalidArgumentException('Negative offset or limit.');

		} elseif (version_compare($this->version, 11, '<')) { // 11 == SQL Server 2012
			if ($offset) {
				throw new Nette\NotSupportedException('Offset is not supported by this database.');

			} elseif ($limit !== NULL) {
				$sql = preg_replace('#^\s*(SELECT(\s+DISTINCT|\s+ALL)?|UPDATE|DELETE)#i', '$0 TOP ' . (int) $limit, $sql, 1, $count);
				if (!$count) {
					throw new Nette\InvalidArgumentException('SQL query must begin with SELECT, UPDATE or DELETE command.');
				}
			}

		} elseif ($limit !== NULL || $offset) {
			// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
			$sql .= ' OFFSET ' . (int) $offset . ' ROWS '
				. 'FETCH NEXT ' . (int) $limit . ' ROWS ONLY';
		}
	}

	/**
	 * Normalizes result row.
	 */
	public function normalizeRow($row)
	{
		return $row;
	}

	/********************* reflection ****************d*g**/

	/**
	 * Returns list of tables.
	 */
	public function getTables()
	{
		$tables = array();
		foreach ($this->connection->query("
			SELECT
				name,
				CASE type
					WHEN 'U' THEN 0
					WHEN 'V' THEN 1
				END AS [view]
			FROM
				sys.objects
			WHERE
				type IN ('U', 'V')
		") as $row) {
			$tables[] = array(
				'name' => $row->name,
				'view' => (bool) $row->view,
			);
		}

		return $tables;
	}

	/**
	 * Returns metadata for all columns in a table.
	 */
	public function getColumns($table)
	{
		$columns = array();
		foreach ($this->connection->query("
			SELECT
				c.name AS name,
				o.name AS [table],
				UPPER(t.name) AS nativetype,
				NULL AS size,
				0 AS unsigned,
				c.is_nullable AS nullable,
				OBJECT_DEFINITION(c.default_object_id) AS [default],
				c.is_identity AS autoincrement,
				CASE WHEN i.index_id IS NULL
					THEN 0
					ELSE 1
				END AS [primary]
			FROM
				sys.columns c
				JOIN sys.objects o ON c.object_id = o.object_id
				LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id
				LEFT JOIN sys.key_constraints k ON o.object_id = k.parent_object_id AND k.type = 'PK'
				LEFT JOIN sys.index_columns i ON k.parent_object_id = i.object_id AND i.index_id = k.unique_index_id AND i.column_id = c.column_id
			WHERE
				o.type IN ('U', 'V')
				AND o.name = {$this->connection->quote($table)}
		") as $row) {
			$row = (array) $row;
			$row['vendor'] = $row;
			$row['unsigned'] = (bool) $row['unsigned'];
			$row['nullable'] = (bool) $row['nullable'];
			$row['autoincrement'] = (bool) $row['autoincrement'];
			$row['primary'] = (bool) $row['primary'];

			$columns[] = $row;
		}

		return $columns;
	}

	/**
	 * Returns metadata for all indexes in a table.
	 */
	public function getIndexes($table)
	{
		$indexes = array();
		foreach ($this->connection->query("
			SELECT
				i.name AS name,
				CASE WHEN i.is_unique = 1 OR i.is_unique_constraint = 1
					THEN 1
					ELSE 0
				END AS [unique],
				i.is_primary_key AS [primary],
				c.name AS [column]
			FROM
				sys.indexes i
				JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
				JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
				JOIN sys.tables t ON i.object_id = t.object_id
			WHERE
				t.name = {$this->connection->quote($table)}
			ORDER BY
				i.index_id,
				ic.index_column_id
		") as $row) {
			$indexes[$row->name]['name'] = $row->name;
			$indexes[$row->name]['unique'] = (bool) $row->unique;
			$indexes[$row->name]['primary'] = (bool) $row->primary;
			$indexes[$row->name]['columns'][] = $row->column;
		}

		return array_values($indexes);
	}

	/**
	 * Returns metadata for all foreign keys in a table.
	 */
	public function getForeignKeys($table)
	{
		// Does't work with multicolumn foreign keys
		$keys = array();
		foreach ($this->connection->query("
			SELECT
				fk.name AS name,
				cl.name AS local,
				tf.name AS [table],
				cf.name AS [column]
			FROM
				sys.foreign_keys fk
				JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
				JOIN sys.tables tl ON fkc.parent_object_id = tl.object_id
				JOIN sys.columns cl ON fkc.parent_object_id = cl.object_id AND fkc.parent_column_id = cl.column_id
				JOIN sys.tables tf ON fkc.referenced_object_id = tf.object_id
				JOIN sys.columns cf ON fkc.referenced_object_id = cf.object_id AND fkc.referenced_column_id = cf.column_id
			WHERE
				tl.name = {$this->connection->quote($table)}
		") as $row) {
			$keys[$row->name] = (array) $row;
		}

		return array_values($keys);
	}

	/**
	 * Returns associative array of detected types (IReflection::FIELD_*) in result set.
	 */
	public function getColumnTypes(\PDOStatement $statement)
	{
		$types = array();
		$count = $statement->columnCount();
		for ($col = 0; $col < $count; $col++) {
			$meta = $statement->getColumnMeta($col);
			if (isset($meta['sqlsrv:decl_type']) && $meta['sqlsrv:decl_type'] !== 'timestamp') { // timestamp does not mean time in sqlsrv
				$types[$meta['name']] = Nette\Database\Helpers::detectType($meta['sqlsrv:decl_type']);
			} elseif (isset($meta['native_type'])) {
				$types[$meta['name']] = Nette\Database\Helpers::detectType($meta['native_type']);
			}
		}
		return $types;
	}

	/**
	 * @param  string
	 * @return bool
	 */
	public function isSupported($item)
	{
		return $item === self::SUPPORT_SUBSELECT;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

use Nette;
use Tracy;

/**
 * Database helpers.
 */
class Helpers
{
	/** @var int maximum SQL length */
	public static $maxLength = 100;

	/** @var array */
	public static $typePatterns = array(
		'^_' => IStructure::FIELD_TEXT, // PostgreSQL arrays
		'BYTEA|BLOB|BIN' => IStructure::FIELD_BINARY,
		'TEXT|CHAR|POINT|INTERVAL' => IStructure::FIELD_TEXT,
		'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => IStructure::FIELD_INTEGER,
		'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => IStructure::FIELD_FLOAT,
		'^TIME$' => IStructure::FIELD_TIME,
		'TIME' => IStructure::FIELD_DATETIME, // DATETIME, TIMESTAMP
		'DATE' => IStructure::FIELD_DATE,
		'BOOL' => IStructure::FIELD_BOOL,
	);

	/**
	 * Displays complete result set as HTML table for debug purposes.
	 * @return void
	 */
	public static function dumpResult(ResultSet $result)
	{
		echo "\n
\n
" . htmlSpecialChars($result->getQueryString(), ENT_IGNORE, 'UTF-8') . "
\n";
		if (!$result->getColumnCount()) {
			echo "\t
\n\t\tAffected rows:	\n\t\t", $result->getRowCount(), "	\n\t
\n
\n";
			return;
		}
		$i = 0;
		foreach ($result as $row) {
			if ($i === 0) {
				echo "
\n\t
\n\t\t#row	\n";
				foreach ($row as $col => $foo) {
					echo "\t\t" . htmlSpecialChars($col, ENT_NOQUOTES, 'UTF-8') . "	\n";
				}
				echo "\t
\n
\n
\n";
			}
			echo "\t
\n\t\t", $i, "	\n";
			foreach ($row as $col) {
				echo "\t\t", htmlSpecialChars($col, ENT_NOQUOTES, 'UTF-8'), "	\n";
			}
			echo "\t
\n";
			$i++;
		}

		if ($i === 0) {
			echo "\t
\n\t\tempty result set	\n\t
\n
\n";
		} else {
			echo "
\n
\n";
		}
	}

	/**
	 * Returns syntax highlighted SQL command.
	 * @param  string
	 * @return string
	 */
	public static function dumpSql($sql, array $params = NULL, Connection $connection = NULL)
	{
		static $keywords1 = 'SELECT|(?:ON\s+DUPLICATE\s+KEY)?UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE';
		static $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|[RI]?LIKE|REGEXP|TRUE|FALSE';

		// insert new lines
		$sql = " $sql ";
		$sql = preg_replace("#(?<=[\\s,(])($keywords1)(?=[\\s,)])#i", "\n\$1", $sql);

		// reduce spaces
		$sql = preg_replace('#[ \t]{2,}#', ' ', $sql);

		$sql = wordwrap($sql, 100);
		$sql = preg_replace('#([ \t]*\r?\n){2,}#', "\n", $sql);

		// syntax highlight
		$sql = htmlSpecialChars($sql, ENT_IGNORE, 'UTF-8');
		$sql = preg_replace_callback("#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is", function ($matches) {
			if (!empty($matches[1])) { // comment
				return '' . $matches[1] . '';

			} elseif (!empty($matches[2])) { // error
				return '' . $matches[2] . '';

			} elseif (!empty($matches[3])) { // most important keywords
				return '' . $matches[3] . '';

			} elseif (!empty($matches[4])) { // other keywords
				return '' . $matches[4] . '';
			}
		}, $sql);

		// parameters
		$sql = preg_replace_callback('#\?#', function () use ($params, $connection) {
			static $i = 0;
			if (!isset($params[$i])) {
				return '?';
			}
			$param = $params[$i++];
			if (is_string($param) && (preg_match('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u', $param) || preg_last_error())) {
				return '<binary>';

			} elseif (is_string($param)) {
				$length = Nette\Utils\Strings::length($param);
				$truncated = Nette\Utils\Strings::truncate($param, Helpers::$maxLength);
				$text = htmlspecialchars($connection ? $connection->quote($truncated) : '\'' . $truncated . '\'', ENT_NOQUOTES, 'UTF-8');
				return '' . $text . '';

			} elseif (is_resource($param)) {
				$type = get_resource_type($param);
				if ($type === 'stream') {
					$info = stream_get_meta_data($param);
				}
				return '<' . htmlSpecialChars($type, ENT_NOQUOTES, 'UTF-8') . ' resource> ';

			} else {
				return htmlspecialchars($param, ENT_NOQUOTES, 'UTF-8');
			}
		}, $sql);

		return '
' . trim($sql) . "
\n";
	}

	/**
	 * Common column type detection.
	 * @return array
	 */
	public static function detectTypes(\PDOStatement $statement)
	{
		$types = array();
		$count = $statement->columnCount(); // driver must be meta-aware, see PHP bugs #53782, #54695
		for ($col = 0; $col < $count; $col++) {
			$meta = $statement->getColumnMeta($col);
			if (isset($meta['native_type'])) {
				$types[$meta['name']] = self::detectType($meta['native_type']);
			}
		}
		return $types;
	}

	/**
	 * Heuristic column type detection.
	 * @param  string
	 * @return string
	 * @internal
	 */
	public static function detectType($type)
	{
		static $cache;
		if (!isset($cache[$type])) {
			$cache[$type] = 'string';
			foreach (self::$typePatterns as $s => $val) {
				if (preg_match("#$s#i", $type)) {
					return $cache[$type] = $val;
				}
			}
		}
		return $cache[$type];
	}

	/**
	 * Import SQL dump from file - extremely fast.
	 * @return int  count of commands
	 */
	public static function loadFromFile(Connection $connection, $file)
	{
		@set_time_limit(0); // @ function may be disabled

		$handle = @fopen($file, 'r'); // @ is escalated to exception
		if (!$handle) {
			throw new Nette\FileNotFoundException("Cannot open file '$file'.");
		}

		$count = 0;
		$delimiter = ';';
		$sql = '';
		$pdo = $connection->getPdo(); // native query without logging
		while (!feof($handle)) {
			$s = rtrim(fgets($handle));
			if (!strncasecmp($s, 'DELIMITER ', 10)) {
				$delimiter = substr($s, 10);

			} elseif (substr($s, -strlen($delimiter)) === $delimiter) {
				$sql .= substr($s, 0, -strlen($delimiter));
				$pdo->exec($sql);
				$sql = '';
				$count++;

			} else {
				$sql .= $s . "\n";
			}
		}
		if (trim($sql) !== '') {
			$pdo->exec($sql);
			$count++;
		}
		fclose($handle);
		return $count;
	}

	public static function createDebugPanel($connection, $explain = TRUE, $name = NULL)
	{
		$panel = new Nette\Bridges\DatabaseTracy\ConnectionPanel($connection);
		$panel->explain = $explain;
		$panel->name = $name;
		Tracy\Debugger::getBar()->addPanel($panel);
		return $panel;
	}

	/**
	 * Reformat source to key -> value pairs.
	 * @return array
	 */
	public static function toPairs(array $rows, $key = NULL, $value = NULL)
	{
		if (!$rows) {
			return array();
		}

		$keys = array_keys((array) reset($rows));
		if (!count($keys)) {
			throw new \LogicException('Result set does not contain any column.');

		} elseif ($key === NULL && $value === NULL) {
			if (count($keys) === 1) {
				list($value) = $keys;
			} else {
				list($key, $value) = $keys;
			}
		}

		$return = array();
		if ($key === NULL) {
			foreach ($rows as $row) {
				$return[] = ($value === NULL ? $row : $row[$value]);
			}
		} else {
			foreach ($rows as $row) {
				$return[is_object($row[$key]) ? (string) $row[$key] : $row[$key]] = ($value === NULL ? $row : $row[$value]);
			}
		}

		return $return;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

use Nette\Database\Conventions\AmbiguousReferenceKeyException;

interface IConventions
{

	/**
	 * Returns primary key for table.
	 * @param  string
	 * @return string|array|NULL
	 */
	function getPrimary($table);

	/**
	 * Returns referenced table & referenced column.
	 * Example:
	 *     (author, book) returns array(book, author_id)
	 *
	 * @param  string  source table
	 * @param  string  referencing key
	 * @return array   array(referenced table, referenced column)
	 * @throws AmbiguousReferenceKeyException
	 */
	function getHasManyReference($table, $key);

	/**
	 * Returns referenced table & referencing column.
	 * Example
	 *     (book, author)      returns array(author, author_id)
	 *     (book, translator)  returns array(author, translator_id)
	 *
	 * @param  string  source table
	 * @param  string  referencing key
	 * @return array   array(referenced table, referencing column)
	 */
	function getBelongsToReference($table, $key);

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

/**
 * Row interface.
 */
interface IRow extends \Traversable, \ArrayAccess
{
}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

/**
 * Container of database result fetched into IRow.
 */
interface IRowContainer extends \Traversable
{

	/**
	 * Fetches single row object.
	 * @return IRow|bool if there is no row
	 */
	function fetch();

	/**
	 * Fetches all rows as associative array.
	 * @param  string column name used for an array key or NULL for numeric index
	 * @param  string column name used for an array value or NULL for the whole row
	 * @return array
	 */
	function fetchPairs($key = NULL, $value = NULL);

	/**
	 * Fetches all rows.
	 * @return IRow[]
	 */
	function fetchAll();

	/**
	 * Fetches all rows and returns associative tree.
	 * @param  string  associative descriptor
	 * @return array
	 */
	function fetchAssoc($path);

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

/**
 * Provides cached reflection for database structure.
 */
interface IStructure
{
	const
		FIELD_TEXT = 'string',
		FIELD_BINARY = 'bin',
		FIELD_BOOL = 'bool',
		FIELD_INTEGER = 'int',
		FIELD_FLOAT = 'float',
		FIELD_DATE = 'date',
		FIELD_TIME = 'time',
		FIELD_DATETIME = 'datetime',
		FIELD_UNIX_TIMESTAMP = 'timestamp',
		FIELD_TIME_INTERVAL = 'timeint';

	/**
	 * Returns tables list.
	 * @return array
	 */
	function getTables();

	/**
	 * Returns table columns list.
	 * @param  string
	 * @return array
	 */
	function getColumns($table);

	/**
	 * Returns table primary key.
	 * @param  string
	 * @return string|array|NULL
	 */
	function getPrimaryKey($table);

	/**
	 * Returns table primary key sequence.
	 * @param  string
	 * @return string|NULL
	 */
	function getPrimaryKeySequence($table);

	/**
	 * Returns hasMany reference.
	 * If a targetTable is not provided, returns references for all tables.
	 * @param  string
	 * @param  string|NULL
	 * @return mixed
	 */
	function getHasManyReference($table, $targetTable = NULL);

	/**
	 * Returns belongsTo reference.
	 * If a column is not provided, returns references for all columns.
	 * @param  string
	 * @param  string|NULL
	 * @return mixed
	 */
	function getBelongsToReference($table, $column = NULL);

	/**
	 * Rebuilds database structure cache.
	 * @return mixed
	 */
	function rebuild();

	/**
	 * Returns true if database cached structure has been rebuilt.
	 * @return bool
	 */
	function isRebuilt();

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

/**
 * Supplemental PDO database driver.
 */
interface ISupplementalDriver
{
	const SUPPORT_SEQUENCE = 'sequence',
		SUPPORT_SELECT_UNGROUPED_COLUMNS = 'ungrouped_cols',
		SUPPORT_MULTI_INSERT_AS_SELECT = 'insert_as_select',
		SUPPORT_MULTI_COLUMN_AS_OR_COND = 'multi_column_as_or',
		SUPPORT_SUBSELECT = 'subselect',
		SUPPORT_SCHEMA = 'schema';

	/**
	 * @return DriverException
	 */
	function convertException(\PDOException $e);

	/**
	 * Delimites identifier for use in a SQL statement.
	 * @param  string
	 * @return string
	 */
	function delimite($name);

	/**
	 * Formats boolean for use in a SQL statement.
	 * @param  bool
	 * @return mixed
	 */
	function formatBool($value);

	/**
	 * Formats date-time for use in a SQL statement.
	 * @return string
	 */
	function formatDateTime(/*\DateTimeInterface*/ $value);

	/**
	 * Formats date-time interval for use in a SQL statement.
	 * @return string
	 */
	//function formatDateInterval(\DateInterval $value);

	/**
	 * Encodes string for use in a LIKE statement.
	 * @param  string
	 * @param  int
	 * @return string
	 */
	function formatLike($value, $pos);

	/**
	 * Injects LIMIT/OFFSET to the SQL query.
	 * @param  string  SQL query that will be modified.
	 * @param  int|NULL
	 * @param  int|NULL
	 * @return void
	 */
	function applyLimit(& $sql, $limit, $offset);

	/**
	 * Normalizes result row.
	 * @param  array
	 * @return array
	 */
	function normalizeRow($row);

	/********************* reflection ****************d*g**/

	/**
	 * Returns list of tables.
	 * @return array of [name [, (bool) view]]
	 */
	function getTables();

	/**
	 * Returns metadata for all columns in a table.
	 * @param  string
	 * @return array of [name, nativetype, primary [, table, fullname, (int) size, (bool) nullable, (mixed) default, (bool) autoincrement, (array) vendor]]
	 */
	function getColumns($table);

	/**
	 * Returns metadata for all indexes in a table.
	 * @param  string
	 * @return array of [name, (array of names) columns [, (bool) unique, (bool) primary]]
	 */
	function getIndexes($table);

	/**
	 * Returns metadata for all foreign keys in a table.
	 * @param  string
	 * @return array
	 */
	function getForeignKeys($table);

	/**
	 * Returns associative array of detected types (IStructure::FIELD_*) in result set.
	 * @param  \PDOStatement
	 * @return array
	 */
	function getColumnTypes(\PDOStatement $statement);

	/**
	 * Cheks if driver supports specific property
	 * @param  string self::SUPPORT_* property
	 * @return bool
	 */
	function isSupported($item);

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

use Nette;
use PDO;

/**
 * Represents a result set.
 *
 * @property-read Connection $connection
 */
class ResultSet extends Nette\Object implements \Iterator, IRowContainer
{
	/** @var Connection */
	private $connection;

	/** @var ISupplementalDriver */
	private $supplementalDriver;

	/** @var \PDOStatement|NULL */
	private $pdoStatement;

	/** @var IRow */
	private $result;

	/** @var int */
	private $resultKey = -1;

	/** @var IRow[] */
	private $results;

	/** @var float */
	private $time;

	/** @var string */
	private $queryString;

	/** @var array */
	private $params;

	/** @var array */
	private $types;

	public function __construct(Connection $connection, $queryString, array $params)
	{
		$time = microtime(TRUE);
		$this->connection = $connection;
		$this->supplementalDriver = $connection->getSupplementalDriver();
		$this->queryString = $queryString;
		$this->params = $params;

		try {
			if (substr($queryString, 0, 2) === '::') {
				$connection->getPdo()->{substr($queryString, 2)}();
			} elseif ($queryString !== NULL) {
				static $types = array('boolean' => PDO::PARAM_BOOL, 'integer' => PDO::PARAM_INT,
					'resource' => PDO::PARAM_LOB, 'NULL' => PDO::PARAM_NULL);
				$this->pdoStatement = $connection->getPdo()->prepare($queryString);
				foreach ($params as $key => $value) {
					$type = gettype($value);
					$this->pdoStatement->bindValue(is_int($key) ? $key + 1 : $key, $value, isset($types[$type]) ? $types[$type] : PDO::PARAM_STR);
				}
				$this->pdoStatement->setFetchMode(PDO::FETCH_ASSOC);
				$this->pdoStatement->execute();
			}
		} catch (\PDOException $e) {
			$e = $this->supplementalDriver->convertException($e);
			$e->queryString = $queryString;
			throw $e;
		}
		$this->time = microtime(TRUE) - $time;
	}

	/**
	 * @return Connection
	 */
	public function getConnection()
	{
		return $this->connection;
	}

	/**
	 * @internal
	 * @return \PDOStatement
	 */
	public function getPdoStatement()
	{
		return $this->pdoStatement;
	}

	/**
	 * @return string
	 */
	public function getQueryString()
	{
		return $this->queryString;
	}

	/**
	 * @return array
	 */
	public function getParameters()
	{
		return $this->params;
	}

	/**
	 * @return int
	 */
	public function getColumnCount()
	{
		return $this->pdoStatement ? $this->pdoStatement->columnCount() : NULL;
	}

	/**
	 * @return int
	 */
	public function getRowCount()
	{
		return $this->pdoStatement ? $this->pdoStatement->rowCount() : NULL;
	}

	/**
	 * @return float
	 */
	public function getTime()
	{
		return $this->time;
	}

	/**
	 * Normalizes result row.
	 * @param  array
	 * @return array
	 */
	public function normalizeRow($row)
	{
		if ($this->types === NULL) {
			$this->types = (array) $this->supplementalDriver->getColumnTypes($this->pdoStatement);
		}

		foreach ($this->types as $key => $type) {
			$value = $row[$key];
			if ($value === NULL || $value === FALSE || $type === IStructure::FIELD_TEXT) {

			} elseif ($type === IStructure::FIELD_INTEGER) {
				$row[$key] = is_float($tmp = $value * 1) ? $value : $tmp;

			} elseif ($type === IStructure::FIELD_FLOAT) {
				if (($pos = strpos($value, '.')) !== FALSE) {
					$value = rtrim(rtrim($pos === 0 ? "0$value" : $value, '0'), '.');
				}
				$float = (float) $value;
				$row[$key] = (string) $float === $value ? $float : $value;

			} elseif ($type === IStructure::FIELD_BOOL) {
				$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';

			} elseif ($type === IStructure::FIELD_DATETIME || $type === IStructure::FIELD_DATE || $type === IStructure::FIELD_TIME) {
				$row[$key] = new Nette\Utils\DateTime($value);

			} elseif ($type === IStructure::FIELD_TIME_INTERVAL) {
				preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
				$row[$key] = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
				$row[$key]->invert = (int) (bool) $m[1];

			} elseif ($type === IStructure::FIELD_UNIX_TIMESTAMP) {
				$row[$key] = Nette\Utils\DateTime::from($value);
			}
		}

		return $this->supplementalDriver->normalizeRow($row);
	}

	/********************* misc tools ****************d*g**/

	/**
	 * Displays complete result set as HTML table for debug purposes.
	 * @return void
	 */
	public function dump()
	{
		Helpers::dumpResult($this);
	}

	/********************* interface Iterator ****************d*g**/

	public function rewind()
	{
		if ($this->result === FALSE) {
			throw new Nette\InvalidStateException('Nette\\Database\\ResultSet implements only one way iterator.');
		}
	}

	public function current()
	{
		return $this->result;
	}

	public function key()
	{
		return $this->resultKey;
	}

	public function next()
	{
		$this->result = FALSE;
	}

	public function valid()
	{
		if ($this->result) {
			return TRUE;
		}

		return $this->fetch() !== FALSE;
	}

	/********************* interface IRowContainer ****************d*g**/

	/**
	 * @inheritDoc
	 */
	public function fetch()
	{
		$data = $this->pdoStatement ? $this->pdoStatement->fetch() : NULL;
		if (!$data) {
			$this->pdoStatement->closeCursor();
			return FALSE;
		}

		$row = new Row;
		foreach ($this->normalizeRow($data) as $key => $value) {
			if ($key !== '') {
				$row->$key = $value;
			}
		}

		if ($this->result === NULL && count($data) !== $this->pdoStatement->columnCount()) {
			trigger_error('Found duplicate columns in database result set.', E_USER_NOTICE);
		}

		$this->resultKey++;
		return $this->result = $row;
	}

	/**
	 * Fetches single field.
	 * @param  int
	 * @return mixed|FALSE
	 */
	public function fetchField($column = 0)
	{
		$row = $this->fetch();
		return $row ? $row[$column] : FALSE;
	}

	/**
	 * @inheritDoc
	 */
	public function fetchPairs($key = NULL, $value = NULL)
	{
		return Helpers::toPairs($this->fetchAll(), $key, $value);
	}

	/**
	 * @inheritDoc
	 */
	public function fetchAll()
	{
		if ($this->results === NULL) {
			$this->results = iterator_to_array($this);
		}
		return $this->results;
	}

	/**
	 * @inheritDoc
	 */
	public function fetchAssoc($path)
	{
		return Nette\Utils\Arrays::associate($this->fetchAll(), $path);
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

use Nette;

/**
 * Represents a single table row.
 */
class Row extends Nette\Utils\ArrayHash implements IRow
{

	public function __get($key)
	{
		$hint = Nette\Utils\ObjectMixin::getSuggestion(array_keys((array) $this), $key);
		throw new Nette\MemberAccessException("Cannot read an undeclared column '$key'" . ($hint ? ", did you mean '$hint'?" : '.'));
	}

	/**
	 * Returns a item.
	 * @param  mixed  key or index
	 * @return mixed
	 */
	public function offsetGet($key)
	{
		if (is_int($key)) {
			$arr = array_slice((array) $this, $key, 1);
			if (!$arr) {
				throw new Nette\MemberAccessException("Cannot read an undeclared column '$key'.");
			}
			return current($arr);
		}
		return $this->$key;
	}

	/**
	 * Checks if $key exists.
	 * @param  mixed  key or index
	 * @return bool
	 */
	public function offsetExists($key)
	{
		if (is_int($key)) {
			return (bool) current(array_slice((array) $this, $key, 1));
		}
		return parent::offsetExists($key);
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

use Nette;

/**
 * SQL literal value.
 */
class SqlLiteral extends Nette\Object
{
	/** @var string */
	private $value;

	/** @var array */
	private $parameters;

	public function __construct($value, array $parameters = array())
	{
		$this->value = (string) $value;
		$this->parameters = $parameters;
	}

	/**
	 * @return array
	 */
	public function getParameters()
	{
		return $this->parameters;
	}

	/**
	 * @return string
	 */
	public function __toString()
	{
		return $this->value;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

use Nette;

/**
 * SQL preprocessor.
 */
class SqlPreprocessor extends Nette\Object
{
	/** @var Connection */
	private $connection;

	/** @var ISupplementalDriver */
	private $driver;

	/** @var array of input parameters */
	private $params;

	/** @var array of parameters to be processed by PDO */
	private $remaining;

	/** @var int */
	private $counter;

	/** @var string values|set|and|order */
	private $arrayMode;

	public function __construct(Connection $connection)
	{
		$this->connection = $connection;
		$this->driver = $connection->getSupplementalDriver();
	}

	/**
	 * @param  array
	 * @return array of [sql, params]
	 */
	public function process($params)
	{
		$this->params = $params;
		$this->counter = 0;
		$prev = -1;
		$this->remaining = array();
		$this->arrayMode = NULL;
		$res = array();

		while ($this->counter < count($params)) {
			$param = $params[$this->counter++];

			if (($this->counter === 2 && count($params) === 2) || !is_scalar($param)) {
				$res[] = $this->formatValue($param, 'auto');
				$this->arrayMode = NULL;

			} elseif (is_string($param) && $this->counter > $prev + 1) {
				$prev = $this->counter;
				$this->arrayMode = NULL;
				$res[] = Nette\Utils\Strings::replace(
					$param,
					'~\'[^\']*+\'|"[^"]*+"|\?[a-z]*|^\s*+(?:INSERT|REPLACE)\b|\b(?:SET|WHERE|HAVING|ORDER BY|GROUP BY|KEY UPDATE)(?=[\s?]*+\z)|/\*.*?\*/|--[^\n]*~si',
					array($this, 'callback')
				);
			} else {
				throw new Nette\InvalidArgumentException('There are more parameters than placeholders.');
			}
		}

		return array(implode(' ', $res), $this->remaining);
	}

	/** @internal */
	public function callback($m)
	{
		$m = $m[0];
		if ($m[0] === '?') { // placeholder
			if ($this->counter >= count($this->params)) {
				throw new Nette\InvalidArgumentException('There are more placeholders than passed parameters.');
			}
			return $this->formatValue($this->params[$this->counter++], substr($m, 1) ?: 'auto');

		} elseif ($m[0] === "'" || $m[0] === '"' || $m[0] === '/' || $m[0] === '-') { // string or comment
			return $m;

		} else { // command
			static $modes = array(
				'INSERT' => 'values',
				'REPLACE' => 'values',
				'KEY UPDATE' => 'set',
				'SET' => 'set',
				'WHERE' => 'and',
				'HAVING' => 'and',
				'ORDER BY' => 'order',
				'GROUP BY' => 'order',
			);
			$this->arrayMode = $modes[ltrim(strtoupper($m))];
			return $m;
		}
	}

	private function formatValue($value, $mode = NULL)
	{
		if (!$mode || $mode === 'auto') {
			if (is_string($value)) {
				if (strlen($value) > 20) {
					$this->remaining[] = $value;
					return '?';

				} else {
					return $this->connection->quote($value);
				}

			} elseif (is_int($value)) {
				return (string) $value;

			} elseif (is_float($value)) {
				return rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');

			} elseif (is_bool($value)) {
				return $this->driver->formatBool($value);

			} elseif ($value === NULL) {
				return 'NULL';

			} elseif ($value instanceof Table\IRow) {
				return $value->getPrimary();

			} elseif ($value instanceof SqlLiteral) {
				$prep = clone $this;
				list($res, $params) = $prep->process(array_merge(array($value->__toString()), $value->getParameters()));
				$this->remaining = array_merge($this->remaining, $params);
				return $res;

			} elseif ($value instanceof \DateTime || $value instanceof \DateTimeInterface) {
				return $this->driver->formatDateTime($value);

			} elseif ($value instanceof \DateInterval) {
				return $this->driver->formatDateInterval($value);

			} elseif (is_object($value) && method_exists($value, '__toString')) {
				return $this->formatValue((string) $value);

			} elseif (is_resource($value)) {
				$this->remaining[] = $value;
				return '?';
			}

		} elseif ($mode === 'name') {
			if (!is_string($value)) {
				$type = gettype($value);
				throw new Nette\InvalidArgumentException("Placeholder ?$mode expects string, $type given.");
			}
			return $this->delimite($value);
		}

		if ($value instanceof \Traversable && !$value instanceof Table\IRow) {
			$value = iterator_to_array($value);
		}

		if (is_array($value)) {
			$vx = $kx = array();
			if ($mode === 'auto') {
				$mode = $this->arrayMode;
			}

			if ($mode === 'values') { // (key, key, ...) VALUES (value, value, ...)
				if (array_key_exists(0, $value)) { // multi-insert
					foreach ($value[0] as $k => $v) {
						$kx[] = $this->delimite($k);
					}
					foreach ($value as $val) {
						$vx2 = array();
						foreach ($val as $v) {
							$vx2[] = $this->formatValue($v);
						}
						$vx[] = implode(', ', $vx2);
					}
					$select = $this->driver->isSupported(ISupplementalDriver::SUPPORT_MULTI_INSERT_AS_SELECT);
					return '(' . implode(', ', $kx) . ($select ? ') SELECT ' : ') VALUES (')
						. implode($select ? ' UNION ALL SELECT ' : '), (', $vx) . ($select ? '' : ')');
				}

				foreach ($value as $k => $v) {
					$kx[] = $this->delimite($k);
					$vx[] = $this->formatValue($v);
				}
				return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';

			} elseif (!$mode || $mode === 'set') {
				foreach ($value as $k => $v) {
					if (is_int($k)) { // value, value, ... OR (1, 2), (3, 4)
						$vx[] = is_array($v) ? '(' . $this->formatValue($v) . ')' : $this->formatValue($v);
					} elseif (substr($k, -1) === '=') { // key+=value, key-=value, ...
						$k2 = $this->delimite(substr($k, 0, -2));
						$vx[] = $k2 . '=' . $k2 . ' ' . substr($k, -2, 1) . ' ' . $this->formatValue($v);
					} else { // key=value, key=value, ...
						$vx[] = $this->delimite($k) . '=' . $this->formatValue($v);
					}
				}
				return implode(', ', $vx);

			} elseif ($mode === 'and' || $mode === 'or') { // (key [operator] value) AND ...
				foreach ($value as $k => $v) {
					if (is_int($k)) {
						$vx[] = $this->formatValue($v);
						continue;
					}
					list($k, $operator) = explode(' ', $k . ' ');
					$k = $this->delimite($k);
					if (is_array($v)) {
						if ($v) {
							$vx[] = $k . ' ' . ($operator ? $operator . ' ' : '') . 'IN (' . $this->formatValue(array_values($v)) . ')';
						} elseif ($operator === 'NOT') {
						} else {
							$vx[] = '1=0';
						}
					} else {
						$v = $this->formatValue($v);
						$vx[] = $k . ' ' . ($operator ?: ($v === 'NULL' ? 'IS' : '=')) . ' ' . $v;
					}
				}
				return $value ? '(' . implode(') ' . strtoupper($mode) . ' (', $vx) . ')' : '1=1';

			} elseif ($mode === 'order') { // key, key DESC, ...
				foreach ($value as $k => $v) {
					$vx[] = $this->delimite($k) . ($v > 0 ? '' : ' DESC');
				}
				return implode(', ', $vx);

			} else {
				throw new Nette\InvalidArgumentException("Unknown placeholder ?$mode.");
			}

		} elseif (in_array($mode, array('and', 'or', 'set', 'values', 'order'), TRUE)) {
			$type = gettype($value);
			throw new Nette\InvalidArgumentException("Placeholder ?$mode expects array or Traversable object, $type given.");

		} elseif ($mode && $mode !== 'auto') {
			throw new Nette\InvalidArgumentException("Unknown placeholder ?$mode.");

		} else {
			throw new Nette\InvalidArgumentException('Unexpected type of parameter: ' . (is_object($value) ? get_class($value) : gettype($value)));
		}
	}

	private function delimite($name)
	{
		return implode('.', array_map(array($this->driver, 'delimite'), explode('.', $name)));
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database;

use Nette;

/**
 * Cached reflection of database structure.
 */
class Structure extends Nette\Object implements IStructure
{
	/** @var Connection */
	protected $connection;

	/** @var Nette\Caching\Cache */
	protected $cache;

	/** @var array */
	protected $structure;

	/** @var bool */
	protected $isRebuilt = FALSE;

	public function __construct(Connection $connection, Nette\Caching\IStorage $cacheStorage)
	{
		$this->connection = $connection;
		$this->cache = new Nette\Caching\Cache($cacheStorage, 'Nette.Database.Structure.' . md5($this->connection->getDsn()));
	}

	public function getTables()
	{
		$this->needStructure();
		return $this->structure['tables'];
	}

	public function getColumns($table)
	{
		$this->needStructure();
		$table = $this->resolveFQTableName($table);

		return $this->structure['columns'][$table];
	}

	public function getPrimaryKey($table)
	{
		$this->needStructure();
		$table = $this->resolveFQTableName($table);

		if (!isset($this->structure['primary'][$table])) {
			return NULL;
		}

		return $this->structure['primary'][$table];
	}

	public function getPrimaryKeySequence($table)
	{
		$this->needStructure();
		$table = $this->resolveFQTableName($table);

		if (!$this->connection->getSupplementalDriver()->isSupported(ISupplementalDriver::SUPPORT_SEQUENCE)) {
			return NULL;
		}

		$primary = $this->getPrimaryKey($table);
		if (!$primary || is_array($primary)) {
			return NULL;
		}

		foreach ($this->structure['columns'][$table] as $columnMeta) {
			if ($columnMeta['name'] === $primary) {
				return isset($columnMeta['vendor']['sequence']) ? $columnMeta['vendor']['sequence'] : NULL;
			}
		}

		return NULL;
	}

	public function getHasManyReference($table, $targetTable = NULL)
	{
		$this->needStructure();
		$table = $this->resolveFQTableName($table);

		if ($targetTable) {
			$targetTable = $this->resolveFQTableName($targetTable);
			foreach ($this->structure['hasMany'][$table] as $key => $value) {
				if (strtolower($key) === $targetTable) {
					return $this->structure['hasMany'][$table][$key];
				}
			}

			return NULL;

		} else {
			if (!isset($this->structure['hasMany'][$table])) {
				return array();
			}
			return $this->structure['hasMany'][$table];
		}
	}

	public function getBelongsToReference($table, $column = NULL)
	{
		$this->needStructure();
		$table = $this->resolveFQTableName($table);

		if ($column) {
			$column = strtolower($column);
			if (!isset($this->structure['belongsTo'][$table][$column])) {
				return NULL;
			}
			return $this->structure['belongsTo'][$table][$column];

		} else {
			if (!isset($this->structure['belongsTo'][$table])) {
				return array();
			}
			return $this->structure['belongsTo'][$table];
		}
	}

	public function rebuild()
	{
		$this->structure = $this->loadStructure();
		$this->cache->save('structure', $this->structure);
	}

	public function isRebuilt()
	{
		return $this->isRebuilt;
	}

	protected function needStructure()
	{
		if ($this->structure !== NULL) {
			return;
		}

		$this->structure = $this->cache->load('structure', array($this, 'loadStructure'));
	}

	/**
	 * @internal
	 */
	public function loadStructure()
	{
		$driver = $this->connection->getSupplementalDriver();

		$structure = array();
		$structure['tables'] = $driver->getTables();

		foreach ($structure['tables'] as $tablePair) {
			if (isset($tablePair['fullName'])) {
				$table = $tablePair['fullName'];
				$structure['aliases'][strtolower($tablePair['name'])] = strtolower($table);
			} else {
				$table = $tablePair['name'];
			}

			$structure['columns'][strtolower($table)] = $columns = $driver->getColumns($table);

			if (!$tablePair['view']) {
				$structure['primary'][strtolower($table)] = $this->analyzePrimaryKey($columns);
				$this->analyzeForeignKeys($structure, $table);
			}
		}

		if (isset($structure['hasMany'])) {
			foreach ($structure['hasMany'] as & $table) {
				uksort($table, function ($a, $b) {
					return strlen($a) - strlen($b);
				});
			}
		}

		$this->isRebuilt = TRUE;

		return $structure;
	}

	protected function analyzePrimaryKey(array $columns)
	{
		$primary = array();
		foreach ($columns as $column) {
			if ($column['primary']) {
				$primary[] = $column['name'];
			}
		}

		if (count($primary) === 0) {
			return NULL;
		} elseif (count($primary) === 1) {
			return reset($primary);
		} else {
			return $primary;
		}
	}

	protected function analyzeForeignKeys(& $structure, $table)
	{
		foreach ($this->connection->getSupplementalDriver()->getForeignKeys($table) as $row) {
			$structure['belongsTo'][strtolower($table)][$row['local']] = $row['table'];
			$structure['hasMany'][strtolower($row['table'])][$table][] = $row['local'];
		}

		if (isset($structure['belongsTo'][$table])) {
			uksort($structure['belongsTo'][$table], function ($a, $b) {
				return strlen($a) - strlen($b);
			});
		}
	}

	protected function resolveFQTableName($table)
	{
		$name = strtolower($table);
		if (isset($this->structure['columns'][$name])) {
			return $name;
		}

		if (isset($this->structure['aliases'][$name])) {
			return $this->structure['aliases'][$name];
		}

		if (!$this->isRebuilt()) {
			$this->rebuild();
			return $this->resolveFQTableName($table);
		}

		throw new Nette\InvalidArgumentException("Table '$name' does not exist.");
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Table;

use Nette;

/**
 * Single row representation.
 * ActiveRow is based on the great library NotORM http://www.notorm.com written by Jakub Vrana.
 */
class ActiveRow implements \IteratorAggregate, IRow
{
	/** @var Selection */
	private $table;

	/** @var array of row data */
	private $data;

	/** @var bool */
	private $dataRefreshed = FALSE;

	public function __construct(array $data, Selection $table)
	{
		$this->data = $data;
		$this->table = $table;
	}

	/**
	 * @internal
	 */
	public function setTable(Selection $table)
	{
		$this->table = $table;
	}

	/**
	 * @internal
	 */
	public function getTable()
	{
		return $this->table;
	}

	public function __toString()
	{
		try {
			return (string) $this->getPrimary();
		} catch (\Throwable $e) {
		} catch (\Exception $e) {
		}
		if (isset($e)) {
			if (func_num_args()) {
				throw $e;
			}
			trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
		}
	}

	/**
	 * @return array
	 */
	public function toArray()
	{
		$this->accessColumn(NULL);
		return $this->data;
	}

	/**
	 * Returns primary key value.
	 * @param  bool
	 * @return mixed possible int, string, array, object (Nette\Utils\DateTime)
	 */
	public function getPrimary($need = TRUE)
	{
		$primary = $this->table->getPrimary($need);
		if ($primary === NULL) {
			return NULL;

		} elseif (!is_array($primary)) {
			if (isset($this->data[$primary])) {
				return $this->data[$primary];
			} elseif ($need) {
				throw new Nette\InvalidStateException("Row does not contain primary $primary column data.");
			} else {
				return NULL;
			}

		} else {
			$primaryVal = array();
			foreach ($primary as $key) {
				if (!isset($this->data[$key])) {
					if ($need) {
						throw new Nette\InvalidStateException("Row does not contain primary $key column data.");
					} else {
						return NULL;
					}
				}
				$primaryVal[$key] = $this->data[$key];
			}
			return $primaryVal;
		}
	}

	/**
	 * Returns row signature (composition of primary keys)
	 * @param  bool
	 * @return string
	 */
	public function getSignature($need = TRUE)
	{
		return implode('|', (array) $this->getPrimary($need));
	}

	/**
	 * Returns referenced row.
	 * @param  string
	 * @param  string
	 * @return IRow or NULL if the row does not exist
	 */
	public function ref($key, $throughColumn = NULL)
	{
		$row = $this->table->getReferencedTable($this, $key, $throughColumn);
		if ($row === FALSE) {
			throw new Nette\MemberAccessException("No reference found for \${$this->table->name}->ref($key).");
		}

		return $row;
	}

	/**
	 * Returns referencing rows.
	 * @param  string
	 * @param  string
	 * @return GroupedSelection
	 */
	public function related($key, $throughColumn = NULL)
	{
		$groupedSelection = $this->table->getReferencingTable($key, $throughColumn, $this[$this->table->getPrimary()]);
		if (!$groupedSelection) {
			throw new Nette\MemberAccessException("No reference found for \${$this->table->name}->related($key).");
		}

		return $groupedSelection;
	}

	/**
	 * Updates row.
	 * @param  array|\Traversable (column => value)
	 * @return bool
	 */
	public function update($data)
	{
		if ($data instanceof \Traversable) {
			$data = iterator_to_array($data);
		}

		$primary = $this->getPrimary();
		if (!is_array($primary)) {
			$primary = array($this->table->getPrimary() => $primary);
		}

		$selection = $this->table->createSelectionInstance()
			->wherePrimary($primary);

		if ($selection->update($data)) {
			if ($tmp = array_intersect_key($data, $primary)) {
				$selection = $this->table->createSelectionInstance()
					->wherePrimary($tmp + $primary);
			}
			$selection->select('*');
			if (($row = $selection->fetch()) === FALSE) {
				throw new Nette\InvalidStateException('Database refetch failed; row does not exist!');
			}
			$this->data = $row->data;
			return TRUE;
		} else {
			return FALSE;
		}
	}

	/**
	 * Deletes row.
	 * @return int number of affected rows
	 */
	public function delete()
	{
		$res = $this->table->createSelectionInstance()
			->wherePrimary($this->getPrimary())
			->delete();

		if ($res > 0 && ($signature = $this->getSignature(FALSE))) {
			unset($this->table[$signature]);
		}

		return $res;
	}

	/********************* interface IteratorAggregate ****************d*g**/

	public function getIterator()
	{
		$this->accessColumn(NULL);
		return new \ArrayIterator($this->data);
	}

	/********************* interface ArrayAccess & magic accessors ****************d*g**/

	/**
	 * Stores value in column.
	 * @param  string column name
	 * @param  string value
	 * @return void
	 */
	public function offsetSet($key, $value)
	{
		$this->__set($key, $value);
	}

	/**
	 * Returns value of column.
	 * @param  string column name
	 * @return string
	 */
	public function offsetGet($key)
	{
		return $this->__get($key);
	}

	/**
	 * Tests if column exists.
	 * @param  string column name
	 * @return bool
	 */
	public function offsetExists($key)
	{
		return $this->__isset($key);
	}

	/**
	 * Removes column from data.
	 * @param  string column name
	 * @return void
	 */
	public function offsetUnset($key)
	{
		$this->__unset($key);
	}

	public function __set($key, $value)
	{
		throw new Nette\DeprecatedException('ActiveRow is read-only; use update() method instead.');
	}

	/**
	 * @param  string
	 * @return ActiveRow|mixed
	 * @throws Nette\MemberAccessException
	 */
	public function &__get($key)
	{
		if ($this->accessColumn($key)) {
			return $this->data[$key];
		}

		$referenced = $this->table->getReferencedTable($this, $key);
		if ($referenced !== FALSE) {
			$this->accessColumn($key, FALSE);
			return $referenced;
		}

		$this->removeAccessColumn($key);
		$hint = Nette\Utils\ObjectMixin::getSuggestion(array_keys($this->data), $key);
		throw new Nette\MemberAccessException("Cannot read an undeclared column '$key'" . ($hint ? ", did you mean '$hint'?" : '.'));
	}

	public function __isset($key)
	{
		if ($this->accessColumn($key)) {
			return isset($this->data[$key]);
		}
		$this->removeAccessColumn($key);
		return FALSE;
	}

	public function __unset($key)
	{
		throw new Nette\DeprecatedException('ActiveRow is read-only.');
	}

	/**
	 * @internal
	 */
	public function accessColumn($key, $selectColumn = TRUE)
	{
		if ($this->table->accessColumn($key, $selectColumn) && !$this->dataRefreshed) {
			if (!isset($this->table[$this->getSignature()])) {
				throw new Nette\InvalidStateException('Database refetch failed; row does not exist!');
			}
			$this->data = $this->table[$this->getSignature()]->data;
			$this->dataRefreshed = TRUE;
		}
		return isset($this->data[$key]) || array_key_exists($key, $this->data);
	}

	protected function removeAccessColumn($key)
	{
		$this->table->removeAccessColumn($key);
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Table;

use Nette;
use Nette\Database\Context;
use Nette\Database\IConventions;

/**
 * Representation of filtered table grouped by some column.
 * GroupedSelection is based on the great library NotORM http://www.notorm.com written by Jakub Vrana.
 */
class GroupedSelection extends Selection
{
	/** @var Selection referenced table */
	protected $refTable;

	/** @var  mixed current assigned referencing array */
	protected $refCacheCurrent;

	/** @var string grouping column name */
	protected $column;

	/** @var int primary key */
	protected $active;

	/**
	 * Creates filtered and grouped table representation.
	 * @param  Context
	 * @param  IConventions
	 * @param  string  database table name
	 * @param  string  joining column
	 * @param  Selection
	 * @param  Nette\Caching\IStorage|NULL
	 */
	public function __construct(Context $context, IConventions $conventions, $tableName, $column, Selection $refTable, Nette\Caching\IStorage $cacheStorage = NULL)
	{
		$this->refTable = $refTable;
		$this->column = $column;
		parent::__construct($context, $conventions, $tableName, $cacheStorage);
	}

	/**
	 * Sets active group.
	 * @internal
	 * @param  int  primary key of grouped rows
	 * @return GroupedSelection
	 */
	public function setActive($active)
	{
		$this->active = $active;
		return $this;
	}

	public function select($columns)
	{
		if (!$this->sqlBuilder->getSelect()) {
			$this->sqlBuilder->addSelect("$this->name.$this->column");
		}

		return call_user_func_array('parent::select', func_get_args());
	}

	public function order($columns)
	{
		if (!$this->sqlBuilder->getOrder()) {
			// improve index utilization
			$this->sqlBuilder->addOrder("$this->name.$this->column" . (preg_match('~\bDESC\z~i', $columns) ? ' DESC' : ''));
		}

		return call_user_func_array('parent::order', func_get_args());
	}

	/********************* aggregations ****************d*g**/

	public function aggregation($function)
	{
		$aggregation = & $this->getRefTable($refPath)->aggregation[$refPath . $function . $this->getSql() . json_encode($this->sqlBuilder->getParameters())];

		if ($aggregation === NULL) {
			$aggregation = array();

			$selection = $this->createSelectionInstance();
			$selection->getSqlBuilder()->importConditions($this->getSqlBuilder());
			$selection->select($function);
			$selection->select("$this->name.$this->column");
			$selection->group("$this->name.$this->column");

			foreach ($selection as $row) {
				$aggregation[$row[$this->column]] = $row;
			}
		}

		if (isset($aggregation[$this->active])) {
			foreach ($aggregation[$this->active] as $val) {
				return $val;
			}
		}
	}

	public function count($column = NULL)
	{
		$return = parent::count($column);
		return isset($return) ? $return : 0;
	}

	/********************* internal ****************d*g**/

	protected function execute()
	{
		if ($this->rows !== NULL) {
			$this->observeCache = $this;
			return;
		}

		$accessedColumns = $this->accessedColumns;
		$this->loadRefCache();

		if (!isset($this->refCacheCurrent['data'])) {
			// we have not fetched any data yet => init accessedColumns by cached accessedColumns
			$this->accessedColumns = $accessedColumns;

			$limit = $this->sqlBuilder->getLimit();
			$rows = count($this->refTable->rows);
			if ($limit && $rows > 1) {
				$this->sqlBuilder->setLimit(NULL, NULL);
			}
			parent::execute();
			$this->sqlBuilder->setLimit($limit, NULL);
			$data = array();
			$offset = array();
			$this->accessColumn($this->column);
			foreach ((array) $this->rows as $key => $row) {
				$ref = & $data[$row[$this->column]];
				$skip = & $offset[$row[$this->column]];
				if ($limit === NULL || $rows <= 1 || (count($ref) < $limit && $skip >= $this->sqlBuilder->getOffset())) {
					$ref[$key] = $row;
				} else {
					unset($this->rows[$key]);
				}
				$skip++;
				unset($ref, $skip);
			}

			$this->refCacheCurrent['data'] = $data;
			$this->data = & $this->refCacheCurrent['data'][$this->active];
		}

		$this->observeCache = $this;
		if ($this->data === NULL) {
			$this->data = array();
		} else {
			foreach ($this->data as $row) {
				$row->setTable($this); // injects correct parent GroupedSelection
			}
			reset($this->data);
		}
	}

	protected function getRefTable(& $refPath)
	{
		$refObj = $this->refTable;
		$refPath = $this->name . '.';
		while ($refObj instanceof self) {
			$refPath .= $refObj->name . '.';
			$refObj = $refObj->refTable;
		}

		return $refObj;
	}

	protected function loadRefCache()
	{
		$hash = $this->getSpecificCacheKey();
		$referencing = & $this->refCache['referencing'][$this->getGeneralCacheKey()];
		$this->observeCache = & $referencing['observeCache'];
		$this->refCacheCurrent = & $referencing[$hash];
		$this->accessedColumns = & $referencing[$hash]['accessed'];
		$this->specificCacheKey = & $referencing[$hash]['specificCacheKey'];
		$this->rows = & $referencing[$hash]['rows'];

		if (isset($referencing[$hash]['data'][$this->active])) {
			$this->data = & $referencing[$hash]['data'][$this->active];
		}
	}

	protected function emptyResultSet($saveCache = TRUE, $deleteRererencedCache = TRUE)
	{
		parent::emptyResultSet($saveCache, FALSE);
	}

	/********************* manipulation ****************d*g**/

	public function insert($data)
	{
		if ($data instanceof \Traversable && !$data instanceof Selection) {
			$data = iterator_to_array($data);
		}

		if (Nette\Utils\Arrays::isList($data)) {
			foreach (array_keys($data) as $key) {
				$data[$key][$this->column] = $this->active;
			}
		} else {
			$data[$this->column] = $this->active;
		}

		return parent::insert($data);
	}

	public function update($data)
	{
		$builder = $this->sqlBuilder;

		$this->sqlBuilder = clone $this->sqlBuilder;
		$this->where($this->column, $this->active);
		$return = parent::update($data);

		$this->sqlBuilder = $builder;
		return $return;
	}

	public function delete()
	{
		$builder = $this->sqlBuilder;

		$this->sqlBuilder = clone $this->sqlBuilder;
		$this->where($this->column, $this->active);
		$return = parent::delete();

		$this->sqlBuilder = $builder;
		return $return;
	}

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Table;

use Nette\Database;

/**
 * Row interface.
 */
interface IRow extends Database\IRow
{

	function setTable(Selection $name);

	function getTable();

	function getPrimary($need = TRUE);

	function getSignature($need = TRUE);

	function related($key, $throughColumn = NULL);

	function ref($key, $throughColumn = NULL);

}


code:
<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Nette\Database\Table;

use Nette\Database;

/**
 * Container of database result fetched into IRow objects.
 *
 * @method     IRow|bool  fetch() Fetches single row object.
 * @method     IRow[]     fetchAll() Fetches all rows.
 */
interface IRowContainer extends Database\IRowContainer
{
}