diff --git a/composer.json b/composer.json
index 1905ca70eb402faa81307acca62526ff03ae6205..0fe3128a519cffc0173d836673b7013132074330 100644
--- a/composer.json
+++ b/composer.json
@@ -1,10 +1,14 @@
 {
     "name": "mappi/store",
 	"require": {
-		"php": ">=7.0.0"
+		"php": ">=7.0.0",
+        "dibi/dibi": "^3.0",
+        "tracy/tracy": "^2.3"
 	},
     "require-dev": {
-        "nette/tester": "^1.7"
+        "nette/tester": "^1.7",
+        "mockery/mockery": "^0.9.4",
+		"kdyby/tester-extras": "dev-master@dev"
     },
     "authors": [
         {
diff --git a/gulpfile.js b/gulpfile.js
index 3526e369e704ba1bb27d7e4b36d56a7d06b54474..b84e982d19398129a851987437d7835907f25d53 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -14,3 +14,7 @@ gulp.task(
 gulp.task("composer:install", shell.task([
 	"composer install"
 ]));
+
+gulp.task("tests", shell.task([
+	"composer install"
+]));
\ No newline at end of file
diff --git a/src/DibiDataStore.php b/src/DibiDataStore.php
deleted file mode 100644
index 1bca285a0c5fd033e01ab86f0a725f261d4d4db1..0000000000000000000000000000000000000000
--- a/src/DibiDataStore.php
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php declare(strict_types = 1);
-
-/**
- * This file is part of mappi/store.
- */
-class DibiDataStore
-{
-}
\ No newline at end of file
diff --git a/src/PostgresDriver/CursorException.php b/src/PostgresDriver/CursorException.php
new file mode 100644
index 0000000000000000000000000000000000000000..268f27817d1b496b75761fd268f454c0ede6ad43
--- /dev/null
+++ b/src/PostgresDriver/CursorException.php
@@ -0,0 +1,27 @@
+<?php declare(strict_types = 1);
+/**
+ * This file is part of mappi/store.
+ */
+
+namespace Grifart\Mappi\Store\PostgresDriver;
+
+use Exception;
+
+class CursorException extends \LogicException
+{
+	const MESSAGE_NO_DATA_TO_FETCH = "There was not data to fetch. Haven't you reached end of cursor?";
+
+	/**
+	 * @inheritDoc
+	 */
+	public function __construct($message, $code=0, \Exception $previous = null)
+	{
+		parent::__construct($message, $code, $previous);
+	}
+
+	public static function noDataToFetch()
+	{
+		return new static(self::MESSAGE_NO_DATA_TO_FETCH);
+	}
+
+}
\ No newline at end of file
diff --git a/src/PostgresDriver/FastCursor.php b/src/PostgresDriver/FastCursor.php
new file mode 100644
index 0000000000000000000000000000000000000000..36c68bf7fd6b090d46dac4f194f51f374134271d
--- /dev/null
+++ b/src/PostgresDriver/FastCursor.php
@@ -0,0 +1,188 @@
+<?php declare(strict_types = 1);
+/**
+ * This file is part of mappi/store.
+ */
+
+namespace Grifart\Mappi\Store\PostgresDriver;
+
+use Dibi\Connection;
+
+class FastCursor implements ICursor
+{
+
+	/** @var Connection */
+	private $connection;
+
+	/** @var string */
+	private $cursorName;
+
+	/** @var string */
+	private $sql;
+
+	// todo: require only connection and name
+	// todo: cursor declaration will be in competence of factory
+	// todo: close
+	public function __construct(Connection $connection, string $sql, bool $scroll = true)
+	{
+		$this->connection = $connection;
+
+		// declare cursor
+		$connection->query(
+			"DECLARE %n %SQL CURSOR FOR (%SQL)",
+			$this->cursorName = $this->generateCursorName(),
+			$scroll ? "SCROLL" : "NO SCROLL",
+			$this->sql = $sql
+		);
+	}
+
+	public function getConnection() : Connection
+	{
+		return $this->connection;
+	}
+
+	public function getCursorName() : string
+	{
+		return $this->cursorName;
+	}
+
+	private function generateCursorName() : string
+	{
+		return uniqid();
+	}
+
+	public function moveTo(int $index)
+	{
+		$this->getConnection()->query(
+			"MOVE ABSOLUTE %i IN %n",
+			$index,
+			$this->getCursorName()
+		);
+	}
+
+	public function moveBy(int $rows)
+	{
+		$this->getConnection()->query(
+			"MOVE RELATIVE %i IN %n",
+			$rows,
+			$this->getCursorName()
+		);
+	}
+
+	public function moveToBeginning()
+	{
+		$this->getConnection()->query(
+			"MOVE ABSOLUTE 0 IN %n;",
+			$this->getCursorName()
+		);
+	}
+
+	public function moveToFirst()
+	{
+		$this->getConnection()->query(
+			"MOVE FIRST IN %n;", // == ABSOLUTE 1
+			$this->getCursorName()
+		);
+	}
+
+	public function moveToLast()
+	{
+		$this->getConnection()->query(
+			"MOVE LAST IN %n", // == ABSOLUTE -1
+			$this->getCursorName()
+		);
+	}
+
+	public function moveToEnd()
+	{
+		$this->getConnection()->query(
+			"MOVE ABSOLUTE -1 IN %n; MOVE NEXT IN %n;",
+			$this->getCursorName(),
+			$this->getCursorName()
+		);
+	}
+
+	public function fetch(int $rows)
+	{
+		$forward = $rows >= 0;
+		$rows = abs($rows);
+		return $this->connection->query(
+			$forward ? "FETCH FORWARD %i FROM %n" : "FETCH BACKWARD %i FROM %n",
+			$rows,
+			$this->getCursorName()
+		)->fetchAll();
+	}
+
+	public function fetchNext()
+	{
+		// todo: throw and exception when 0 undefined
+		$row = $this->fetch(1);
+		if(isset($row[0])) {
+			return $row[0];
+		}
+		return FALSE;
+	}
+
+	public function fetchCurrent()
+	{
+		return $this->fetchOneBy(0);
+	}
+
+	public function fetchNextSingle()
+	{
+		$row = $this->fetchNext();
+		if($row === FALSE) {
+			throw CursorException::noDataToFetch();
+		}
+		return current(
+			$row->toArray()
+		);
+	}
+
+	public function fetchCurrentSingle()
+	{
+		$row = $this->fetchCurrent();
+		if($row === FALSE) {
+			throw CursorException::noDataToFetch();
+		}
+		return current(
+			$row->toArray()
+		);
+	}
+
+	public function fetchOneAt(int $index)
+	{
+		return $this->connection->query(
+			"FETCH ABSOLUTE %i FROM %n",
+			$index,
+			$this->getCursorName()
+		)->fetch();
+	}
+
+	public function fetchOneBy(int $rows)
+	{
+		return $this->connection->query(
+			"FETCH RELATIVE %i FROM %n",
+			$rows,
+			$this->getCursorName()
+		)->fetch();
+	}
+
+	public function fetchRemaining()
+	{
+		return $this->connection->query(
+			"FETCH FORWARD ALL FROM %n",
+			$this->getCursorName()
+		)->fetchAll();
+	}
+
+	public function fetchForegoing()
+	{
+		return $this->connection->query(
+			"FETCH BACKWARD ALL FROM %n",
+			$this->getCursorName()
+		)->fetchAll();
+	}
+
+
+
+}
\ No newline at end of file
diff --git a/src/PostgresDriver/ICursor.php b/src/PostgresDriver/ICursor.php
new file mode 100644
index 0000000000000000000000000000000000000000..8f2aa0413250e989d1136e168634f47f1ac900a1
--- /dev/null
+++ b/src/PostgresDriver/ICursor.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * This file is part of mappi/store.
+ */
+namespace Grifart\Mappi\Store\PostgresDriver;
+
+/**
+ * Represents PostgreSQL cursor
+ * @link http://www.postgresql.org/docs/9.5/static/plpgsql-cursors.html
+ * @link http://www.postgresql.org/docs/8.1/static/sql-fetch.html
+ * @link http://www.postgresql.org/docs/8.1/static/sql-move.html
+ * @package Grifart\Mappi\Store\PostgresDriver
+ */
+interface ICursor
+{
+	public function getCursorName() : string;
+
+	/**
+	 * @param int $rows
+	 * @return void
+	 */
+	public function moveBy(int $rows);
+
+	/**
+	 * @param int $index
+	 * @return void
+	 */
+	public function moveTo(int $index);
+
+	/**
+	 * Move cursor on the last row
+	 * @see fetchCurrent()
+	 * @see fetchCurrentSingle()
+	 * @return void
+	 */
+	public function moveToLast();
+
+	/**
+	 * Move cursor on the first row (index=1)
+	 * @see fetchCurrent()
+	 * @see fetchCurrentSingle()
+	 * @return void
+	 */
+	public function moveToFirst();
+
+	/**
+	 * Moves cursor to position BEFORE first row (index=0)
+	 * @see fetchNext()
+	 * @see fetchNextSingle()
+	 * @return void
+	 */
+	public function moveToBeginning();
+
+	/**
+	 * Moves cursor to position AFTER last row
+	 * @return void
+	 */
+	public function moveToEnd();
+
+	/**
+	 * Fetch the next/previous count rows. FORWARD 0 re-fetches the current row.
+	 * @param int $rows
+	 * @return array
+	 */
+	public function fetch(int $rows);
+
+	/**
+	 * @return array the row
+	 */
+	public function fetchNext();
+	public function fetchCurrent();
+
+	/**
+	 * @return mixed the value in first column
+	 */
+	public function fetchNextSingle();
+	public function fetchCurrentSingle();
+
+	/**
+	 * Fetch the count'th row of the query, or the abs(count)'th row from the end if count is negative. Position before first row or after last row if count is out of range; in particular, ABSOLUTE 0 positions before the first row.
+	 * @param int $index
+	 * @return array
+	 */
+	public function fetchOneAt(int $index);
+
+	/**
+	 * Fetch the count'th succeeding row, or the abs(count)'th prior row if count is negative. RELATIVE 0 re-fetches the current row, if any.
+	 * @param int $rows
+	 * @return array
+	 */
+	public function fetchOneBy(int $rows);
+
+	/**
+	 * Fetch all remaining rows.
+	 * @return array of remaining rows
+	 */
+	public function fetchRemaining();
+
+	/**
+	 * Fetch all prior rows (scanning backwards).
+	 * @return array of foregoing rows
+	 */
+	public function fetchForegoing();
+}
\ No newline at end of file
diff --git a/tests/BaseTest.php b/tests/BaseTest.php
index ca893e543be884f5846ed4d0ada13b7027299b25..1b7e4c4d815e5377286195321a8fac6fa9517854 100644
--- a/tests/BaseTest.php
+++ b/tests/BaseTest.php
@@ -4,7 +4,7 @@
  * Copyright (c) 2015 Grifart spol. s r.o. (https://grifart.cz)
  */
 
-namespace GrifartTests;
+namespace Grifart\Mappi\Tests\Store;
 
 use Mockery;
 use Tester;
@@ -22,7 +22,7 @@ class BaseTest extends Tester\TestCase
 		parent::tearDown();
 
 		// suppress no assert executed when mocking
-		if (count(\Mockery::getContainer()->getMocks()) > 0) {
+		if (count(Mockery::getContainer()->getMocks()) > 0) {
 			Tester\Assert::true(true);
 		}
 
diff --git a/tests/Store/PostgresDriver/FastCursorTest.phpt b/tests/Store/PostgresDriver/FastCursorTest.phpt
new file mode 100644
index 0000000000000000000000000000000000000000..9066b2f991efdea27ec8a6d1a1b71a96368ca75c
--- /dev/null
+++ b/tests/Store/PostgresDriver/FastCursorTest.phpt
@@ -0,0 +1,239 @@
+<?php
+/**
+ * @testCase
+ */
+
+namespace Grifart\Mappi\Tests\Store\Store\PostgresDriver;
+
+use Grifart\Mappi\Store\PostgresDriver\BasicCursor;
+use Grifart\Mappi\Store\PostgresDriver\CursorException;
+use Grifart\Mappi\Store\PostgresDriver\FastCursor;
+use Grifart\Mappi\Tests\Store\BaseTest;
+use Tester\Assert;
+use Tester\Environment;
+
+require_once __DIR__ . "/../../bootstrap.php";
+
+class FastCursorTest extends BaseTest
+{
+	/** @var FastCursor */
+	private $uut;
+
+	/**
+	 * @inheritDoc
+	 */
+	protected function setUp()
+	{
+		global $connection, $SQL_thousandRowsAscending;
+		$connection->begin();
+
+		$this->uut = new FastCursor($connection, $SQL_thousandRowsAscending, true);
+		parent::setUp();
+	}
+
+	public function tearDown()
+	{
+		global $connection;
+		$connection->rollback();
+
+		parent::tearDown();
+	}
+
+
+	// initial position
+	public function test_givenCursor_whenGetCurrentRow_thenMustFail()
+	{
+		// must fail because initial position is 0
+		Assert::exception(function() {
+			$this->uut->fetchCurrentSingle();
+		}, CursorException::class);
+	}
+
+	public function test_givenInitialPosition_whenFetch_thenGetFirstRow()
+	{
+		Assert::same(1, $this->uut->fetchNextSingle());
+	}
+
+	// fetch( x == 0 )
+	public function test_givenSecondPosition_whenFetch0_thenGetCurrentRow()
+	{
+		// head:           .
+		// index: 0  1  2  3  4
+		// value:    1  2  3  4
+		$this->uut->moveTo(3);
+		Assert::same(3, $this->uut->fetchCurrentSingle());
+
+		$result = $this->uut->fetch(0); // rows backwards + read current
+
+		Assert::count(1, $result);
+		Assert::same(3, current($result[0]->toArray()));
+	}
+
+	// fetch( x > 0 )
+	public function test_givenSecondPosition_whenFetchForward_thenGetRowsAfterCursor()
+	{
+		// head:           .
+		// index: 0  1  2  3  4
+		// value:    1  2  3  4
+		$this->uut->moveTo(3);
+		Assert::same(3, $this->uut->fetchCurrentSingle());
+
+		// head:                 .
+		// index: 0  1  2  3  4  5  6
+		// value:    1  2  3  4  5  6
+		$result = $this->uut->fetch(2); // rows backwards + read current
+		// todo: split into fetch decorator for those special fetch* methods
+
+		Assert::count(2, $result);
+		Assert::same(4, current($result[0]->toArray()));
+		Assert::same(5, current($result[1]->toArray()));
+		Assert::same(5, $this->uut->fetchCurrentSingle());
+	}
+
+	// fetch( x < 0 )
+	public function test_givenSecondPosition_whenFetchBackwards_thenGetRowsBeforeCursor()
+	{
+		// head:           .
+		// index: 0  1  2  3  4
+		// value:    1  2  3  4
+		$this->uut->moveTo(3);
+		Assert::same(3, $this->uut->fetchCurrentSingle());
+
+		// head:     .
+		// index: 0  1  2  3  4
+		// value:    1  2  3  4
+		$result = $this->uut->fetch(-2); // rows backwards + read current
+		// todo: split into fetch decorator for those special fetch* methods
+
+		Assert::count(2, $result);
+		Assert::same(2, current($result[0]->toArray()));
+		Assert::same(1, current($result[1]->toArray()));
+		Assert::same(1, $this->uut->fetchCurrentSingle());
+	}
+
+
+	// moveToBeginning()
+	public function test_givenEndPosition_whenMoveToTheBeginning_thenFetchingNextRowWillBeFirstRow()
+	{
+		// Arrange
+		$this->uut->moveToLast();
+
+		// Act
+		$this->uut->moveToBeginning();
+
+		//Assert
+		Assert::same(1, $this->uut->fetchNextSingle());
+	}
+
+
+	// moveToFirst()
+	public function test_givenEndPosition_whenMoveToTheFirst_thenFetchingCurrentWillBeFirstRow()
+	{
+		// Arrange
+		$this->uut->moveToLast();
+
+		// Act
+		$this->uut->moveToFirst();
+
+		//Assert
+		Assert::same(1, $this->uut->fetchCurrentSingle());
+	}
+
+
+	// moveToEnd()
+	public function test_givenInitialPosition_whenMoveToEnd_thenFetchingOneBacwardsWillBeTheLastRow()
+	{
+		$this->uut->moveToEnd();
+
+		Assert::exception(function() {
+			$this->uut->fetchCurrentSingle();
+		}, CursorException::class, CursorException::MESSAGE_NO_DATA_TO_FETCH);
+		$data = $this->uut->fetch(-1);
+		Assert::count(1, $data);
+		Assert::same(1000, current($data[0]->toArray()));
+	}
+
+
+	// moveToLast()
+	public function test_givenInitialPosition_whenMoveToLast_thenFetchingCurrentWillBeLastRow()
+	{
+		$this->uut->moveToLast();
+		Assert::same(1000, $this->uut->fetchCurrentSingle());
+		Assert::exception(function() {
+			$this->uut->fetchNextSingle();
+		}, CursorException::class, CursorException::MESSAGE_NO_DATA_TO_FETCH);
+	}
+
+
+	// moveBy()
+	public function test_givenInitialPosition_whenMoveBy2_thenGetSecondValue()
+	{
+		$this->uut->moveBy(2);
+		Assert::same(2, $this->uut->fetchCurrentSingle());
+	}
+
+	public function test_givenSecondPosition_whenMoveOneBack_thenGetFirstValue()
+	{
+		$this->uut->moveTo(2);
+		$this->uut->moveBy(-1);
+		Assert::same(1, $this->uut->fetchCurrentSingle());
+	}
+
+
+	// fetchOneAt()
+	public function test_givenInitialPosition_whenFetch5_thenGetFive()
+	{
+		$data = $this->uut->fetchOneAt(5);
+		Assert::same(5, current($data->toArray()));
+	}
+
+	public function test_givenNonInitialPosition_whenFetch5_thenGetFive()
+	{
+		$this->uut->moveTo(234); // wherever
+
+		$data = $this->uut->fetchOneAt(5);
+		Assert::same(5, current($data->toArray()));
+	}
+
+	// fetchRemaining()
+	public function test_given5BeforeEndPosition_whenFetchRemaining_thenGetLastFile()
+	{
+		$this->uut->moveTo(995);
+
+		$result = $this->uut->fetchRemaining();
+
+		Assert::count(5, $result);
+		Assert::same(996,  current($result[0]->toArray()));
+		Assert::same(997,  current($result[1]->toArray()));
+		Assert::same(998,  current($result[2]->toArray()));
+		Assert::same(999,  current($result[3]->toArray()));
+		Assert::same(1000, current($result[4]->toArray()));
+
+		Assert::exception(function() {
+			$this->uut->fetchCurrentSingle();
+		}, CursorException::class, CursorException::MESSAGE_NO_DATA_TO_FETCH);
+	}
+
+
+	// fetchRemaining()
+	public function test_given5AfterStart_whenFetchForegoing_thenGetFirstFive()
+	{
+		$this->uut->moveTo(6);
+
+		$result = $this->uut->fetchForegoing();
+
+		Assert::count(5, $result);
+		Assert::same(5,  current($result[0]->toArray()));
+		Assert::same(4,  current($result[1]->toArray()));
+		Assert::same(3,  current($result[2]->toArray()));
+		Assert::same(2,  current($result[3]->toArray()));
+		Assert::same(1, current($result[4]->toArray()));
+
+		Assert::exception(function() {
+			$this->uut->fetchCurrentSingle();
+		}, CursorException::class, CursorException::MESSAGE_NO_DATA_TO_FETCH);
+	}
+	
+}
+
+(new FastCursorTest())->run();
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index ce2a8b713f32fa584f473ea9e6013a41a59111d1..875931ba87d6d02d4c59e0692396843b45a482ad 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -11,6 +11,28 @@ if (!class_exists('Tester\Assert')) {
 	exit(1);
 }
 
+require __DIR__ . "/BaseTest.php";
+
+$SQL_thousandRowsAscending = <<<SQL
+WITH RECURSIVE t(n) AS (
+		SELECT 1
+	UNION ALL
+		SELECT n+1 FROM t
+)
+SELECT n FROM t LIMIT 1000
+SQL;
+
+$connection = new \Dibi\Connection(
+	[
+		"driver" => "postgre",
+		"host"   => "localhost",
+		"dbname" => "mappi-store-test",
+		"port"   => "5434",
+		"user"   => "postgres",
+		"password"=>"toor"
+	]
+);
+
 Kdyby\TesterExtras\Bootstrap::setup(__DIR__);
 
 Tracy\Debugger::$logDirectory = __DIR__ . '/logs';
diff --git a/tests/php-windows.ini b/tests/php-windows.ini
index 62fd0c6d074f5490d914e0eb63312b913fea096c..094c13d14ad41e4ce938016613a16d735df23d4c 100644
--- a/tests/php-windows.ini
+++ b/tests/php-windows.ini
@@ -1,5 +1,6 @@
 [PHP]
 extension_dir = "./ext"
+extension = php_pgsql.dll
 
 ;[Zend]
 ;zend_extension=php_xdebug-2.3.2-5.6-vc11-nts-x86_64.dll