diff --git a/.idea/cursor.iml b/.idea/cursor.iml
index fb11120cb8f09a4c48b399157fe673b7f581cbd6..4662101fa8da6efdd5dba0eeb5dd055d180781d0 100644
--- a/.idea/cursor.iml
+++ b/.idea/cursor.iml
@@ -3,8 +3,8 @@
   <component name="NewModuleRootManager">
     <content url="file://$MODULE_DIR$">
       <sourceFolder url="file://$MODULE_DIR$/vendor/nette/tester/src" isTestSource="false" packagePrefix="Tester" />
-      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="Grifart\Mappi\Store" generated="true" />
-      <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="Grifart\Mappi\Tests\Store" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="Grifart\Mappi\Cursor" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="Grifart\Mappi\Tests" generated="true" />
       <sourceFolder url="file://$MODULE_DIR$/vendor/mockery/mockery/tests" isTestSource="true" packagePrefix="test" />
       <sourceFolder url="file://$MODULE_DIR$/vendor/dibi/dibi/src" isTestSource="false" />
       <sourceFolder url="file://$MODULE_DIR$/vendor/hamcrest/hamcrest-php/tests" isTestSource="true" />
@@ -12,6 +12,9 @@
       <sourceFolder url="file://$MODULE_DIR$/vendor/mockery/mockery/library" isTestSource="false" />
       <sourceFolder url="file://$MODULE_DIR$/vendor/mockery/mockery/tests" isTestSource="true" />
       <sourceFolder url="file://$MODULE_DIR$/vendor/mockery/mockery/tests/Mockery/Test" isTestSource="true" packagePrefix="Mockery" />
+      <sourceFolder url="file://$MODULE_DIR$/vendor/marc-mabe/php-enum/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/vendor/tracy/tracy/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/vendor/tracy/tracy/src" isTestSource="false" packagePrefix="Tracy" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 5b8593b5db5e5594f7121bb88628ce64c5136c9a..fe19a3834c84e6b870cab7101024ad1969b1c1f6 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -2,7 +2,7 @@
 <project version="4">
   <component name="ProjectModuleManager">
     <modules>
-      <module fileurl="file://$PROJECT_DIR$/../cursor/.idea/cursor.iml" filepath="$PROJECT_DIR$/../cursor/.idea/cursor.iml" />
+      <module fileurl="file://$PROJECT_DIR$/.idea/cursor.iml" filepath="$PROJECT_DIR$/.idea/cursor.iml" />
     </modules>
   </component>
 </project>
\ No newline at end of file
diff --git a/README.md b/README.md
index b2f167550e2aad9e8590decfc930d071de21bf6a..53af6bc8352587b493d0f7d813db9edcea9984e8 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,31 @@
-# mappi/store
+# mappi/cursor
 
-Provides API for reading data from database using cursors. 
-This allows you to read huge data without fetching everything into memory.
\ No newline at end of file
+Provides API for reading data from database using cursors. This allows you to read huge data without all of them into memory.
+
+Example usage:
+````php
+use Grifart\Mappi\Cursor;
+$connection = new \Dibi\Connection([/* ... */]);
+$cursorFactory = new Cursor\Driver\PostgresCursorFactory($connection);
+
+// cursor allow to do basic operations
+$cursor = $cursorFactory->create("SELECT * FROM mytable", TRUE);
+
+// tracked cursor can tell current position
+$trackedCursor = new Cursor\TrackedCursor($cursor, Cursor\Position::fromLeft(0));
+
+// semantic cursor provides nicer API for cursor
+$semanticCursor = new Cursor\SemanticCursor($cursor);
+
+while($row = $semanticCursor->fetchNext()) {
+    echo $trackedCursor->getPosition();
+    print_r($row);
+    echo "<hr>";
+}
+
+while($row = $semanticCursor->fetchPrevious()) {
+    echo $trackedCursor->getPosition();
+    print_r($row);
+    echo "<hr>";
+}
+````
\ No newline at end of file
diff --git a/examples/basic-example.php b/examples/basic-example.php
new file mode 100644
index 0000000000000000000000000000000000000000..34257cabbb0af3cb4954ac9442532f7137e4940b
--- /dev/null
+++ b/examples/basic-example.php
@@ -0,0 +1,45 @@
+<?php declare(strict_types = 1);
+
+require_once __DIR__ . "/../vendor/autoload.php";
+
+use Grifart\Mappi\Cursor;
+
+// using PostgreSQL 9.5 cursor simulation using in-memory data
+$driver = new \Grifart\Mappi\Tests\Cursor\Driver\ArrayCursorDriver();
+$driver->createTestCursor("test-cursor", 26684);
+
+// cursor allow to do basic operations
+$cursor = new Cursor\Cursor($driver, "test-cursor");
+
+// tracked cursor can tell current position
+$trackedCursor = new Cursor\TrackedCursor($cursor, Cursor\Position::fromLeft(0));
+
+// semantic cursor provides nicer API for cursor
+$semanticCursor = new Cursor\SemanticTrackedCursor($trackedCursor);
+
+echo "<pre>\n";
+
+echo "first row: " . print_r($semanticCursor->fetchNext(), true) . "\n";
+
+
+$start = 547;
+$limit = 20;
+
+echo "position before move: " . $trackedCursor->getPosition() . "\n";
+$semanticCursor->moveTo(547);
+echo "position after move: " . $trackedCursor->getPosition() . "\n";
+
+echo "\nposition\tvalue\n";
+$i = $start;
+foreach($trackedCursor->fetchRange($limit) as $row) {
+	$i++;
+	/** @var array $row */
+	echo $i . "\t\t" . json_encode($row) . "\n";
+}
+echo "\n";
+echo "position after fetchRange(): " . $trackedCursor->getPosition() . "\n";
+
+echo "There is " . $semanticCursor->getTotal() . " rows in the data set\n";
+echo "position after getTotal(): " . $trackedCursor->getPosition() . "\n";
+
+echo "</pre>";
diff --git a/src/ArrayCursorDriver.php b/src/ArrayCursorDriver.php
deleted file mode 100644
index f982f03fe8d35aed221dc2c92424f6cddbbce378..0000000000000000000000000000000000000000
--- a/src/ArrayCursorDriver.php
+++ /dev/null
@@ -1,171 +0,0 @@
-<?php declare(strict_types = 1);
-/**
- * This file is part of mappi/cursor.
- */
-
-namespace Grifart\Mappi\Cursor;
-
-/**
- * Array Cursor simulates scrolling cursor as implemented in PostgreSQL 9.5.
- *
- * For more information see ICursor.
- * @see ICursor
- * @package Grifart\Mappi\Cursor
- */
-class ArrayCursorDriver implements ICursorDriver
-{
-	private $cursors = [];
-	private $indexes = [];
-
-	public function createTestCursor(string $name, int $length)
-	{
-		if(isset($this->cursors[$name])) {
-			throw new CursorException("Cursor with name $name already exists");
-		}
-
-		$this->cursors[$name] = range(0, $length+1);
-		$this->cursors[$name][0] = null;
-		$this->cursors[$name][$length+1] = null;
-
-		$this->indexes[$name] = 0;
-	}
-
-	public function close(string $name) : bool
-	{
-		unset($this->cursors[$name]);
-		return TRUE;
-	}
-
-	private function getMaxIndex(string $name) {
-		return count($this->cursors[$name])-1;
-	}
-
-	private function normalizeIndex(string $name, int &$index)
-	{
-		if($index < 0) {
-			$index = 0;
-		}
-		$max = $this->getMaxIndex($name);
-		if($index > $max) {
-			$index = $max;
-		}
-	}
-
-	private function pointerMoveTo(string $name, int $index)
-	{
-		$this->normalizeIndex($name, $index);
-		$this->indexes[$name] = $index;
-	}
-
-	private function getCurrentIndex(string $name) : int
-	{
-		return $this->indexes[$name];
-	}
-
-	private function getCurrentValue(string $name) {
-		return $this->cursors[$name][$this->getCurrentIndex($name)];
-	}
-
-	public function moveTo(string $name, int $index) : bool
-	{
-		if ($index < 0) {
-			throw new \InvalidArgumentException("Negative index not supported. Use moveFromEndTo() instead.");
-		}
-		$this->pointerMoveTo($name, $index);
-		return !!$this->getCurrentValue($name);
-	}
-
-	private function cursorArrayMoveFromEndTo(string $name, int $index)
-	{
-		$max = $this->getMaxIndex($name);
-
-		$indexFromLeft = $max - $index;
-		$this->normalizeIndex($name, $indexFromLeft);
-
-		$this->moveTo($name, $indexFromLeft);
-	}
-
-	public function moveFromEndTo(string $name, int $index) : bool
-	{
-		if ($index < 0) {
-			throw new \InvalidArgumentException("Negative index not supported. Use moveTo() instead.");
-		}
-		$this->cursorArrayMoveFromEndTo($name, $index);
-		return !!$this->getCurrentValue($name);
-	}
-
-	public function moveBy(string $name, int $offset) : bool
-	{
-		$i = $this->getCurrentIndex($name);
-		$indexFromLeft = $i + $offset;
-		$this->normalizeIndex($name, $indexFromLeft);
-		$this->moveTo($name, $indexFromLeft);
-		return !!$this->getCurrentValue($name);
-	}
-
-	// ----------------- FETCH ---------------------
-
-	public function fetchRange(string $name, int $offset) : array
-	{
-		$currentKey = $this->getCurrentIndex($name);
-		if($offset === self::FETCH_FOREGOING) {
-			$finalKey = PHP_INT_MIN;
-		} elseif ($offset === self::FETCH_REMAINING) {
-			$finalKey = PHP_INT_MAX;
-		} else {
-			$finalKey = $currentKey + $offset;
-		}
-		$this->normalizeIndex($name, $finalKey);
-
-		$data = [];
-		if($offset === 0) {
-			$v = $this->getCurrentValue($name);
-			if($v !== NULL) {
-				$data[] = ["n" => $this->getCurrentValue($name)];
-			}
-			return $data;
-		}
-
-		if($finalKey < $currentKey) { // backwards
-			for($i = $currentKey-1; $i >= $finalKey; $i--) {
-				$this->moveTo($name, $i);
-				$v = $this->getCurrentValue($name);
-				if($v === NULL) {break;}
-				$data[] = ["n" => $this->getCurrentValue($name)];
-			}
-		} else {
-			for($i = $currentKey+1; $i <= $finalKey; $i++) {
-				$this->moveTo($name, $i);
-				$v = $this->getCurrentValue($name);
-				if($v === NULL) {break;}
-				$data[] = ["n" => $this->getCurrentValue($name)];
-			}
-		}
-		return $data;
-	}
-
-	public function fetchOneAt(string $name, int $index)
-	{
-		if($index < 0) {
-			$this->moveFromEndTo($name, abs($index));
-		} else {
-			$this->moveTo($name, $index);
-		}
-		$data = $this->fetchRange($name, 0);
-		if(count($data) === 0) {
-			return NULL;
-		}
-		return $data[0];
-	}
-
-	public function fetchOneBy(string $name, int $offset)
-	{
-		$this->moveBy($name, $offset);
-		$data = $this->fetchRange($name, 0);
-		if(count($data) === 0) {
-			return NULL;
-		}
-		return $data[0];
-	}
-
-}
\ No newline at end of file
diff --git a/src/Cursor.php b/src/Cursor.php
index 70b1bbc8372b956fc893b5040fdec747d53251ba..ca66d34b14c50021fedc049053b0f6e01508cc93 100644
--- a/src/Cursor.php
+++ b/src/Cursor.php
@@ -4,6 +4,7 @@
  */
 
 namespace Grifart\Mappi\Cursor;
+use Grifart\Mappi\Cursor\Driver\ICursorDriver;
 
 /**
  * PostgreSQL cursor driver
@@ -56,7 +57,7 @@ final class Cursor implements ICursor
 		if ($index < 0) {
 			throw new \InvalidArgumentException("Negative index not supported. Use moveFromEndTo() instead.");
 		}
-		$this->headOnRecord = $this->driver->moveTo($this->name, $index);
+		$this->headOnRecord = $this->driver->moveFromBeginningTo($this->name, $index);
 	}
 
 	public function moveFromEndTo(int $index)
@@ -73,6 +74,13 @@ final class Cursor implements ICursor
 		$this->headOnRecord = $this->driver->moveBy($this->name, $offset);
 	}
 
+	public function scroll(int $offset) : int
+	{
+		$numberOfRows = $this->driver->scroll($this->name, $offset);
+		$this->headOnRecord = $numberOfRows === abs($offset);
+		return $numberOfRows;
+	}
+
 	// ----------------- FETCH ---------------------
 
 	public function fetchRange(int $offset) : array
diff --git a/src/CursorPosition.php b/src/CursorPosition.php
deleted file mode 100644
index 19383d6f5f3328c01fbc1db361e43fee502e0e22..0000000000000000000000000000000000000000
--- a/src/CursorPosition.php
+++ /dev/null
@@ -1,102 +0,0 @@
-<?php declare(strict_types = 1);
-/**
- * This file is part of mappi/cursor.
- */
-
-namespace Grifart\Mappi\Cursor;
-
-// todo: make this immutable as VOs should be
-
-/**
- * Value object for TrackedCursor position
- *
- * @link    https://github.com/nicolopignatelli/valueobjects (inspiration)
- * @package Grifart\Mappi\Cursor
- */
-final class CursorPosition
-{
-	/** @var CursorPositionOrigin */
-	private $origin;
-
-	/** @var int the cursor position */
-	private $position = 0;
-
-	/**
-	 * @param CursorPositionOrigin $origin   from left or from right?
-	 * @param int                  $position which position
-	 */
-	public function __construct(CursorPositionOrigin $origin, int $position)
-	{
-		$this->setPosition($origin, $position);
-	}
-
-	public static function fromLeft(int $position) : self
-	{
-		return new self(
-			CursorPositionOrigin::get(CursorPositionOrigin::FROM_LEFT),
-			$position
-		);
-	}
-
-	public static function fromRight(int $position) : self
-	{
-		return new self(
-			CursorPositionOrigin::get(CursorPositionOrigin::FROM_RIGHT),
-			$position
-		);
-	}
-
-	public function setPositionFromLeft(int $position)
-	{
-		$this->setPosition(CursorPositionOrigin::get(CursorPositionOrigin::FROM_LEFT), $position);
-	}
-
-	public function setPositionFromRight(int $position)
-	{
-		$this->setPosition(CursorPositionOrigin::get(CursorPositionOrigin::FROM_RIGHT), $position);
-	}
-
-	private function setPosition(CursorPositionOrigin $origin, int $position)
-	{
-		if($position < 0) {
-			throw CursorException::cursorPosition_invalidIndexValue($position);
-		}
-		$this->origin = $origin;
-		$this->position = $position;
-	}
-
-	/**
-	 * @param int $by negative = left; positive = right
-	 */
-	public function movePositionBy(int $by)
-	{
-		$modifier = 1;
-		if ($this->origin->is(CursorPositionOrigin::FROM_RIGHT)) {
-			$modifier = -1;
-		}
-		$this->position += $modifier * $by;
-	}
-
-	/**
-	 * From which side is position counted?
-	 * @return CursorPositionOrigin
-	 */
-	public function getOrigin()
-	{
-		return $this->origin;
-	}
-
-	/**
-	 * Cursor position from origin
-	 * @return int
-	 */
-	public function getPosition()
-	{
-		return $this->position;
-	}
-
-	function __toString() : string
-	{
-		return ($this->origin->getValue() === CursorPositionOrigin::FROM_LEFT ? "left:" : "right:") . $this->position;
-	}
-}
\ No newline at end of file
diff --git a/src/ICursorDriver.php b/src/Driver/ICursorDriver.php
similarity index 86%
rename from src/ICursorDriver.php
rename to src/Driver/ICursorDriver.php
index e4b0d97b44e7c2b31a33276595579ba9b7e67113..aa076dccfb1b1d5537e67aa681f4a6338f32dab8 100644
--- a/src/ICursorDriver.php
+++ b/src/Driver/ICursorDriver.php
@@ -2,7 +2,7 @@
 /**
  * This file is part of mappi/cursor.
  */
-namespace Grifart\Mappi\Cursor;
+namespace Grifart\Mappi\Cursor\Driver;
 
 /**
  * Represents cursor
@@ -33,7 +33,7 @@ interface ICursorDriver
 	 * @param int    $index
 	 * @return bool Head on record?
 	 */
-	public function moveTo(string $name, int $index) : bool;
+	public function moveFromBeginningTo(string $name, int $index) : bool;
 
 	/**
 	 * Move cursor from the END to given position
@@ -51,6 +51,14 @@ interface ICursorDriver
 	 */
 	public function moveBy(string $name, int $offset) : bool;
 
+	/**
+	 * Scroll from current position by given offset
+	 * @param string $name The cursor
+	 * @param int    $offset
+	 * @return int Number of records scrolled
+	 */
+	public function scroll(string $name, int $offset) : int;
+
 	/**
 	 * Fetch all rows from current position to given offset
 	 * @param string $name The cursor name
diff --git a/src/PostgresCursorDriver.php b/src/Driver/PostgresCursorDriver.php
similarity index 80%
rename from src/PostgresCursorDriver.php
rename to src/Driver/PostgresCursorDriver.php
index 6d58bdfcfdb73a63c40b2860d3b9e282d886ffce..8dca1338ceb14a1e1f40dad37993ef9a5f3c39ad 100644
--- a/src/PostgresCursorDriver.php
+++ b/src/Driver/PostgresCursorDriver.php
@@ -3,7 +3,7 @@
  * This file is part of mappi/cursor.
  */
 
-namespace Grifart\Mappi\Cursor;
+namespace Grifart\Mappi\Cursor\Driver;
 
 use Dibi\Connection;
 use Dibi\Row;
@@ -31,7 +31,7 @@ class PostgresCursorDriver implements ICursorDriver
 		return $this->connection;
 	}
 
-	public function moveTo(string $name, int $index) : bool
+	public function moveFromBeginningTo(string $name, int $index) : bool
 	{
 		if ($index < 0) {
 			throw new \InvalidArgumentException("Negative index not supported. Use moveFromEndTo() instead.");
@@ -73,6 +73,33 @@ class PostgresCursorDriver implements ICursorDriver
 		);
 	}
 
+	public function scroll(string $name, int $offset) : int
+	{
+		if($offset === 0) return 0;
+		$forward = $offset > 0;
+		$offsetSql = abs($offset);
+
+		if($forward > 0) {
+			if($offset === self::FETCH_REMAINING) {
+				$offsetSql = "ALL";
+			}
+			return $this->getConnection()->query(
+				"MOVE FORWARD %sql IN %n",
+				$offsetSql,
+				$name
+			);
+ 		} else {
+			if($offset === self::FETCH_FOREGOING) {
+				$offsetSql = "ALL";
+			}
+			return $this->getConnection()->query(
+				"MOVE BACKWARD %sql IN %n",
+				$offsetSql,
+				$name
+			);
+		}
+	}
+
 	// ----------------- FETCH ---------------------
 
 	public function fetchRange(string $name, int $offset) : array
diff --git a/src/PostgresCursorFactory.php b/src/Driver/PostgresCursorFactory.php
similarity index 91%
rename from src/PostgresCursorFactory.php
rename to src/Driver/PostgresCursorFactory.php
index 67b08d88127262dff22f74fabe39e99059543f9b..e2f2468fae982e830536beb10874ade7f097f1b0 100644
--- a/src/PostgresCursorFactory.php
+++ b/src/Driver/PostgresCursorFactory.php
@@ -3,9 +3,11 @@
  * This file is part of mappi/cursor.
  */
 
-namespace Grifart\Mappi\Cursor;
+namespace Grifart\Mappi\Cursor\Driver;
 
 use Dibi\Connection;
+use Grifart\Mappi\Cursor\Cursor;
+use Grifart\Mappi\Cursor\ICursor;
 
 final class PostgresCursorFactory
 {
diff --git a/src/ICursor.php b/src/ICursor.php
index 67ae8f55b8d35686df7404ab3e97e06c238454a6..730382c1fa681a6d7340f98df6f7d33fb1a03bb2 100644
--- a/src/ICursor.php
+++ b/src/ICursor.php
@@ -57,6 +57,13 @@ interface ICursor
 	 */
 	public function moveFromEndTo(int $index);
 
+	/**
+	 * Move cursor by given offset and return number of scrolled rows
+	 * @param int $offset
+	 * @return int The number of scrolled rows
+	 */
+	public function scroll(int $offset) : int;
+
 	/**
 	 * Fetch the next/previous count of rows. If zero given current row is fetched.
 	 * Cursor position will be on the last fetched row.
diff --git a/src/Position.php b/src/Position.php
new file mode 100644
index 0000000000000000000000000000000000000000..8a8e9ef19a087e8bd452952ec515ded5bd71d78f
--- /dev/null
+++ b/src/Position.php
@@ -0,0 +1,84 @@
+<?php declare(strict_types = 1);
+/**
+ * This file is part of mappi/cursor.
+ */
+
+namespace Grifart\Mappi\Cursor;
+
+/**
+ * Value Object that holds position of cursor
+ * use by {@see TrackedCursor}
+ *
+ * @link    https://github.com/nicolopignatelli/valueobjects (inspiration)
+ * @package Grifart\Mappi\Cursor
+ */
+final class Position
+{
+	/** @var PositionOrigin */
+	private $origin;
+
+	/** @var int the cursor position */
+	private $position = 0;
+
+	/**
+	 * @param PositionOrigin $origin   from left or from right?
+	 * @param int            $position which position
+	 */
+	private function __construct(PositionOrigin $origin, int $position)
+	{
+		if($position < 0) {
+			throw CursorException::cursorPosition_invalidIndexValue($position);
+		}
+		$this->origin = $origin;
+		$this->position = $position;
+	}
+
+	public static function fromLeft(int $position) : self
+	{
+		return new self(
+			PositionOrigin::get(PositionOrigin::FROM_LEFT),
+			$position
+		);
+	}
+
+	public static function fromRight(int $position) : self
+	{
+		return new self(
+			PositionOrigin::get(PositionOrigin::FROM_RIGHT),
+			$position
+		);
+	}
+	
+	public static function relativeTo(Position $position, int $offset) : self
+	{
+		$positionDelta = ($position->getOrigin()
+			->is(PositionOrigin::FROM_LEFT) ? 1 : -1) * $offset;
+		return new self(
+			$position->getOrigin(),
+			$position->getPosition() + $positionDelta
+		);
+	}
+
+	/**
+	 * From which side is position counted?
+	 * @return PositionOrigin
+	 */
+	public function getOrigin()
+	{
+		return $this->origin;
+	}
+
+	/**
+	 * Cursor position from origin
+	 * @return int
+	 */
+	public function getPosition()
+	{
+		return $this->position;
+	}
+
+	function __toString() : string
+	{
+		return ($this->origin->getValue() === PositionOrigin::FROM_LEFT ? "left:" : "right:") . $this->position;
+	}
+}
\ No newline at end of file
diff --git a/src/CursorPositionOrigin.php b/src/PositionOrigin.php
similarity index 82%
rename from src/CursorPositionOrigin.php
rename to src/PositionOrigin.php
index 41dabea689623fabd2cc317b486c10212afbfd26..89b18f242ee193b6ba4d5a237a07e8ded0274e3d 100644
--- a/src/CursorPositionOrigin.php
+++ b/src/PositionOrigin.php
@@ -7,7 +7,7 @@ namespace Grifart\Mappi\Cursor;
 
 use MabeEnum\Enum;
 
-class CursorPositionOrigin extends Enum
+class PositionOrigin extends Enum
 {
 	const FROM_LEFT = 0;
 	const FROM_RIGHT   = 1;
diff --git a/src/SemanticCursor.php b/src/SemanticCursor.php
index 2c618e1f5ed97802709f8ebf9641ea9577804dfd..68da21e005677794d25cd3fef2d96c3aa77fe908 100644
--- a/src/SemanticCursor.php
+++ b/src/SemanticCursor.php
@@ -19,7 +19,7 @@ namespace Grifart\Mappi\Cursor;
 class SemanticCursor implements ICursor
 {
 	/** @var ICursor */
-	private $cursor;
+	protected $cursor;
 
 	/**
 	 * @param ICursor $cursor
@@ -71,6 +71,22 @@ class SemanticCursor implements ICursor
 		$this->moveFromEndTo(0);
 	}
 
+	/**
+	 * Scrolls to the END
+	 */
+	public function scrollToEnd() : int
+	{
+		return $this->scroll(ICursor::FETCH_REMAINING);
+	}
+
+	/**
+	 * Scroll to the BEGINNING
+	 */
+	public function scrollToBeginning() : int
+	{
+		return $this->scroll(ICursor::FETCH_FOREGOING);
+	}
+
 	// ---- FETCH ----
 
 	/**
@@ -227,6 +243,12 @@ class SemanticCursor implements ICursor
 		$this->cursor->moveBy($offset);
 	}
 
+	/** {@inheritdoc} */
+	public function scroll(int $offset) : int
+	{
+		return $this->cursor->scroll($offset);
+	}
+
 	/** {@inheritdoc} */
 	public function fetchRange(int $offset) : array
 	{
diff --git a/src/SemanticTrackedCursor.php b/src/SemanticTrackedCursor.php
new file mode 100644
index 0000000000000000000000000000000000000000..52fd6e8077674e90a512733958cb1cd6f8945dc4
--- /dev/null
+++ b/src/SemanticTrackedCursor.php
@@ -0,0 +1,48 @@
+<?php declare(strict_types = 1);
+/**
+ * This file is part of mappi/cursor.
+ */
+
+namespace Grifart\Mappi\Cursor;
+
+/**
+ * Semantic cursor with tracked extensions
+ * @todo remove inheritance and use composition instead
+ * @todo add tests
+ * @package Grifart\Mappi\Cursor
+ */
+class SemanticTrackedCursor extends SemanticCursor
+{
+	/** @var TrackedCursor */
+	protected $cursor;
+
+	/**
+	 * @param TrackedCursor $cursor
+	 */
+	public function __construct(TrackedCursor $cursor)
+	{
+		parent::__construct($cursor);
+	}
+
+	public function getPosition() : Position
+	{
+		return $this->cursor->getPosition();
+	}
+
+	/**
+	 * Get total number of rows in cursor
+	 * Caution! This will scroll through cursor to the BEGINNING or to the END
+	 * @return int the number of rows in cursor
+	 */
+	public function getTotal() : int
+	{
+		$fromLeft = $this->cursor->getPosition()->getOrigin()->is(PositionOrigin::FROM_LEFT);
+		if($fromLeft) {
+			$this->scrollToEnd();
+		} else {
+			$this->scrollToBeginning();
+		}
+		return $this->cursor->getPosition()->getPosition() - 1;
+	}
+
+}
\ No newline at end of file
diff --git a/src/TrackedCursor.php b/src/TrackedCursor.php
index b4ddd27feac99a5004cc39a76c6913247d4bafc7..4352aca0c5f20eac9a72c0de126661135bd919af 100644
--- a/src/TrackedCursor.php
+++ b/src/TrackedCursor.php
@@ -7,7 +7,7 @@ namespace Grifart\Mappi\Cursor;
 
 class TrackedCursor implements ICursor
 {
-	/** @var CursorPosition */
+	/** @var Position */
 	private $position;
 
 	/** @var ICursor */
@@ -16,17 +16,17 @@ class TrackedCursor implements ICursor
 	/**
 	 * Tip: if you are not sure that cursor will be in initial state, call
 	 * ->moveToBeginning() after initialization.
-	 * @param ICursor        $cursor Cursor in initial state (index=0)
-	 * @param CursorPosition $initialPosition
+	 * @param ICursor  $cursor Cursor in initial state (index=0)
+	 * @param Position $initialPosition
 	 */
-	public function __construct(ICursor $cursor, CursorPosition $initialPosition)
+	public function __construct(ICursor $cursor, Position $initialPosition)
 	{
 		$this->cursor = $cursor;
 		$this->position = $initialPosition;
 	}
 
 	/**
-	 * @return CursorPosition
+	 * @return Position
 	 */
 	public function getPosition()
 	{
@@ -47,18 +47,16 @@ class TrackedCursor implements ICursor
 	{
 		$this->cursor->moveTo($index);
 		if ($this->cursor->isOnRecord()) {
-			$this->position->setPositionFromLeft(
-				$index
-			);
+			$this->position = Position::fromLeft($index);
 			return;
 		}
 
 		if ($index === 0) {
-			$this->position->setPositionFromLeft(0); // BEGINNING
+			$this->position = Position::fromLeft(0); // BEGINNING
 			return;
 		}
 		if ($index > 0 ) {
-			$this->position->setPositionFromRight(0); // END
+			$this->position = Position::fromRight(0); // END
 			return;
 		}
 		throw CursorException::cursorPosition_unknownError();
@@ -68,18 +66,16 @@ class TrackedCursor implements ICursor
 	{
 		$this->cursor->moveFromEndTo($index);
 		if ($this->cursor->isOnRecord()) {
-			$this->position->setPositionFromRight(
-				$index
-			);
+			$this->position = Position::fromRight($index);
 			return;
 		}
 
 		if ($index === 0) {
-			$this->position->setPositionFromRight(0); // END
+			$this->position = Position::fromRight(0); // END
 			return;
 		}
 		if ($index > 0) {
-			$this->position->setPositionFromLeft(0); // BEGINNING
+			$this->position = Position::fromLeft(0); // BEGINNING
 			return;
 		}
 		throw CursorException::cursorPosition_unknownError();
@@ -87,18 +83,17 @@ class TrackedCursor implements ICursor
 
 	public function moveBy(int $offset)
 	{
-		// todo: use MOVE FORWARD n IN ... which returns number of rows read
 		$this->cursor->moveBy($offset);
 		if ($this->cursor->isOnRecord()) {
-			$this->position->movePositionBy($offset);
+			$this->position = Position::relativeTo($this->position, $offset);
 			return;
 		}
 		if ($offset < 0) {
-			$this->position->setPositionFromLeft(0); // BEGINNING
+			$this->position = Position::fromLeft(0); // BEGINNING
 			return;
 		}
 		if ($offset > 0) {
-			$this->position->setPositionFromRight(0); // END
+			$this->position = Position::fromRight(0); // END
 			return;
 		}
 		if ($offset === 0) {
@@ -107,6 +102,26 @@ class TrackedCursor implements ICursor
 		throw CursorException::cursorPosition_unknownError();
 	}
 
+	public function scroll(int $offset) : int
+	{
+		$wasOnRecord = $this->isOnRecord();
+		$rowsRead = $cursorDelta = $this->cursor->scroll($offset);
+		$forward = $offset >= 0;
+
+		// scroll over the whole data-set
+		$scrolledOverWholeSet = $rowsRead > 0 && $wasOnRecord === FALSE && !$this->isOnRecord();
+		$wasOnRecordAndHitEnd = $wasOnRecord === TRUE && !$this->isOnRecord();
+		if($scrolledOverWholeSet || $wasOnRecordAndHitEnd) {
+			$cursorDelta++;
+		}
+
+		$this->position = Position::relativeTo(
+			$this->position,
+			($forward ? 1 : -1) * $cursorDelta
+		);
+		return $rowsRead;
+	}
+
 	public function fetchRange(int $offset): array
 	{
 		$result = $this->cursor->fetchRange($offset);
@@ -118,17 +133,18 @@ class TrackedCursor implements ICursor
 		$count = count($result);
 		$reachedEnd = $count < abs($offset);
 		if(!$reachedEnd) {
-			$this->position->movePositionBy(
+			$this->position = Position::relativeTo(
+				$this->position,
 				$count * ($forward ? 1 : -1)
 			);
 			return $result;
 		}
 
 		if($forward) {
-			$this->position->setPositionFromRight(0); // right END
+			$this->position = Position::fromRight(0); // right END
 			return $result;
 		} else {
-			$this->position->setPositionFromLeft(0); // left END
+			$this->position = Position::fromLeft(0); // left END
 			return $result;
 		}
 	}
@@ -138,24 +154,24 @@ class TrackedCursor implements ICursor
 		$row = $this->cursor->fetchOneAt($index);
 		if ($row !== NULL) {
 			if($index < 0) {
-				$this->position->setPositionFromRight(abs($index));
+				$this->position = Position::fromRight(abs($index));
 			} else /* >= 0 */ {
-				$this->position->setPositionFromLeft($index);
+				$this->position = Position::fromLeft(abs($index));
 			}
 			return $row;
 		}
 
 		if ($index === 0) {
-			$this->position->setPositionFromLeft(0); // BEGINNING
+			$this->position = Position::fromLeft(0); // BEGINNING
 			return NULL;
 		}
 
 		if ($index < 0) { // went from right to left -> hit beginning
-			$this->position->setPositionFromLeft(0);
+			$this->position = Position::fromLeft(0);
 			return NULL;
 		}
 		if ($index > 0) { // went left->right -> hit end
-			$this->position->setPositionFromRight(0);
+			$this->position = Position::fromRight(0);
 			return NULL;
 		}
 
@@ -166,16 +182,16 @@ class TrackedCursor implements ICursor
 	{ // todo: content tests
 		$row = $this->cursor->fetchOneBy($offset);
 		if ($row !== NULL) {
-			$this->position->movePositionBy($offset);
+			$this->position = $this->position = Position::relativeTo($this->position, $offset);
 			return $row;
 		}
 
 		if ($offset < 0) {
-			$this->position->setPositionFromLeft(0); // BEGINNING
+			$this->position = Position::fromLeft(0); // BEGINNING
 			return NULL;
 		}
 		if ($offset > 0) {
-			$this->position->setPositionFromRight(0); // END
+			$this->position = Position::fromRight(0); // END
 			return NULL;
 		}
 		if ($offset === 0) {
diff --git a/tests/Cursor/CursorTest.phpt b/tests/Cursor/CursorTest.phpt
index 2473cb48f3325b949d10139679e6ff9d9bc5eaaa..efa87419bec1c1088576f745155b23eb7829c03f 100644
--- a/tests/Cursor/CursorTest.phpt
+++ b/tests/Cursor/CursorTest.phpt
@@ -3,10 +3,10 @@
  * @testCase
  */
 
-namespace Grifart\Mappi\Tests\Store\Cursor;
+namespace Grifart\Mappi\Tests\Cursor;
 
-use Grifart\Mappi\Cursor\ArrayCursorDriver;
 use Grifart\Mappi\Cursor\Cursor;
+use Grifart\Mappi\Tests\Cursor\Driver\ArrayCursorDriver;
 use Grifart\Mappi\Cursor\ICursor;
 
 require_once __DIR__ . "/../bootstrap.php";
@@ -19,11 +19,6 @@ class CursorTest extends ICursorTest
 
 	protected function setUp()
 	{
-		//global $connection, $SQL_thousandRowsAscending;
-		//$connection->begin();
-
-		//$factory = new PostgresCursorFactory($connection);
-		//$this->uut = $factory->create($SQL_thousandRowsAscending, true);
 		$driver = new ArrayCursorDriver();
 		$driver->createTestCursor("test", 1000);
 		$this->uut = new Cursor(
@@ -34,14 +29,6 @@ class CursorTest extends ICursorTest
 		parent::setUp();
 	}
 
-	public function tearDown()
-	{
-//		global $connection;
-//		$connection->rollback();
-
-		parent::tearDown();
-	}
-
 }
 
 (new CursorTest())->run();
diff --git a/tests/Cursor/Driver/ArrayCursorDriver.php b/tests/Cursor/Driver/ArrayCursorDriver.php
new file mode 100644
index 0000000000000000000000000000000000000000..8c29678094f03c149b3d737798a074e0f22d6fcd
--- /dev/null
+++ b/tests/Cursor/Driver/ArrayCursorDriver.php
@@ -0,0 +1,222 @@
+<?php declare(strict_types = 1);
+/**
+ * This file is part of mappi/cursor.
+ */
+
+namespace Grifart\Mappi\Tests\Cursor\Driver;
+use Grifart\Mappi\Cursor\CursorException;
+use Grifart\Mappi\Cursor\Driver\ICursorDriver;
+
+/**
+ * Array Cursor simulates scrolling cursor as implemented in PostgreSQL 9.5.
+ *
+ * For more information see ICursor.
+ * @see ICursor
+ * @package Grifart\Mappi\Cursor
+ */
+class ArrayCursorDriver implements ICursorDriver
+{
+	private $cursors = [];
+	private $indexes = [];
+
+	/**
+	 * Creates test cursor with given name
+	 * @param string $name
+	 * @param int    $length
+	 */
+	public function createTestCursor(string $name, int $length)
+	{
+		if(isset($this->cursors[$name])) {
+			throw new CursorException("Cursor with name $name already exists");
+		}
+
+		$this->cursors[$name] = $this->generateCursorData($length);
+
+		$this->indexes[$name] = 0;
+	}
+
+	/**
+	 * Closes test cursor
+	 * @param string $name
+	 * @return bool
+	 */
+	public function close(string $name) : bool
+	{
+		unset($this->cursors[$name]);
+		unset($this->indexes[$name]);
+		return TRUE;
+	}
+
+	/**
+	 * Cursor data is table with one column with name "n" which contains current row index as value
+	 * @param int $length length of values
+	 * @return array
+	 */
+	private function generateCursorData(int $length): array
+	{
+		$data = [];
+		$data[0] = null;
+		$data[$length+1] = null;
+		for($i = 1; $i <= $length; $i++) {
+			$data[$i] = ["n" => $i];
+		}
+		return $data;
+	}
+
+	/**
+	 * Returns END position index from LEFT
+	 * @param string $name The cursor name
+	 * @return int
+	 */
+	private function getMaxIndex(string $name) {
+		return count($this->cursors[$name])-1;
+	}
+
+	/**
+	 * Normalizes index to be between BEGINNING and END of cursor
+	 * @param string $name The cursor name
+	 * @param int    $index The index to normalize
+	 */
+	private function normalizeIndex(string $name, int &$index)
+	{
+		if($index < 0) {
+			$index = 0;
+		}
+		$max = $this->getMaxIndex($name);
+		if($index > $max) {
+			$index = $max;
+		}
+	}
+
+	/**
+	 * Moves internal cursor pointer to position from BEGINNING
+	 * @param string $name The cursor name
+	 * @param int    $index The index from the BEGINNING; index is normalized before move
+	 */
+	private function pointerMoveToFromBeginning(string $name, int $index)
+	{
+		$this->normalizeIndex($name, $index);
+		$this->indexes[$name] = $index;
+	}
+
+	/**
+	 * Moves internal cursor pointer to position from the END
+	 * @param string $name The cursor name
+	 * @param int    $index The index from the ENG; 
+	 */
+	private function pointerMoveToFromEnd(string $name, int $index)
+	{
+		$indexFromLeft = $this->getMaxIndex($name) - $index;
+		$this->normalizeIndex($name, $indexFromLeft);
+
+		$this->moveFromBeginningTo($name, $indexFromLeft);
+	}
+	
+	private function getCurrentIndexFromBeginning(string $name) : int
+	{
+		return $this->indexes[$name];
+	}
+
+	private function retrieveCurrentRow(string $name) {
+		return $this->cursors[$name][$this->getCurrentIndexFromBeginning($name)];
+	}
+
+	public function moveFromBeginningTo(string $name, int $index) : bool
+	{
+		if ($index < 0) {
+			throw new \InvalidArgumentException("Negative index not supported. Use moveFromEndTo() instead.");
+		}
+		$this->pointerMoveToFromBeginning($name, $index);
+		return !!$this->retrieveCurrentRow($name);
+	}
+
+	public function moveFromEndTo(string $name, int $index) : bool
+	{
+		if ($index < 0) {
+			throw new \InvalidArgumentException("Negative index not supported. Use moveTo() instead.");
+		}
+		$this->pointerMoveToFromEnd($name, $index);
+		return !!$this->retrieveCurrentRow($name);
+	}
+
+	public function moveBy(string $name, int $offset) : bool
+	{
+		$i = $this->getCurrentIndexFromBeginning($name);
+		$indexFromLeft = $i + $offset;
+		$this->normalizeIndex($name, $indexFromLeft);
+		$this->moveFromBeginningTo($name, $indexFromLeft);
+		return !!$this->retrieveCurrentRow($name);
+	}
+
+	public function scroll(string $name, int $offset) : int
+	{
+		if($offset === 0) return 0;
+		return count($this->fetchRange($name, $offset));
+	}
+
+	// ----------------- FETCH ---------------------
+
+	public function fetchRange(string $name, int $offset) : array
+	{
+		$currentKey = $this->getCurrentIndexFromBeginning($name);
+		if($offset === self::FETCH_FOREGOING) {
+			$finalKey = PHP_INT_MIN;
+		} elseif ($offset === self::FETCH_REMAINING) {
+			$finalKey = PHP_INT_MAX;
+		} else {
+			$finalKey = $currentKey + $offset;
+		}
+		$this->normalizeIndex($name, $finalKey);
+
+		$data = [];
+		if($offset === 0) {
+			$v = $this->retrieveCurrentRow($name);
+			if($v !== NULL) {
+				$data[] = $v;
+			}
+			return $data;
+		}
+
+		if($finalKey < $currentKey) { // backwards
+			for($i = $currentKey-1; $i >= $finalKey; $i--) {
+				$this->moveFromBeginningTo($name, $i);
+				$v = $this->retrieveCurrentRow($name);
+				if($v === NULL) {break;}
+				$data[] = $v;
+			}
+		} else {
+			for($i = $currentKey+1; $i <= $finalKey; $i++) {
+				$this->moveFromBeginningTo($name, $i);
+				$v = $this->retrieveCurrentRow($name);
+				if($v === NULL) {break;}
+				$data[] = $v;
+			}
+		}
+		return $data;
+	}
+
+	public function fetchOneAt(string $name, int $index)
+	{
+		if($index < 0) {
+			$this->moveFromEndTo($name, abs($index));
+		} else {
+			$this->moveFromBeginningTo($name, abs($index));
+		}
+		$data = $this->fetchRange($name, 0);
+		if(count($data) === 0) {
+			return NULL;
+		}
+		return $data[0];
+	}
+
+	public function fetchOneBy(string $name, int $offset)
+	{
+		$this->moveBy($name, $offset);
+		$data = $this->fetchRange($name, 0);
+		if(count($data) === 0) {
+			return NULL;
+		}
+		return $data[0];
+	}
+
+}
\ No newline at end of file
diff --git a/tests/Cursor/ICursorTest.php b/tests/Cursor/ICursorTest.php
index 5c45a8213c1a744fe27bb95391e0f0ad965cb0ca..ec82cb1d5a029efd9882bee30ec7dbe1a759c71a 100644
--- a/tests/Cursor/ICursorTest.php
+++ b/tests/Cursor/ICursorTest.php
@@ -3,7 +3,7 @@
  * @testCase
  */
 
-namespace Grifart\Mappi\Tests\Store\Cursor;
+namespace Grifart\Mappi\Tests\Cursor;
 
 use Grifart\Mappi\Cursor\ICursor;
 use Grifart\Mappi\Tests\Store\BaseTest;
@@ -15,7 +15,7 @@ use Tester\Assert;
  * This file is using Given-When-Then naming convention.
  * @link http://martinfowler.com/bliki/GivenWhenThen.html
  *
- * @package Grifart\Mappi\Tests\Store\Cursor
+ * @package Grifart\Mappi\Tests\Cursor
  */
 abstract class ICursorTest extends BaseTest
 {
@@ -183,6 +183,49 @@ abstract class ICursorTest extends BaseTest
 		$this->uut->moveBy(-1);
 		Assert::null($this->helper_fetchCurrentSingle());
 	}
+	
+	// scroll()
+	public function test_givenInitialPosition_whenMoveToEnd_thenGetTotalCount()
+	{
+		Assert::same(1000, $this->uut->scroll(ICursor::FETCH_REMAINING));
+		Assert::null($this->helper_fetchCurrentSingle());
+	}
+
+	public function test_givenFifthPosition_whenMoveToEndByIncrements_thenGetRestOfCount()
+	{
+		$this->uut->moveTo(5);
+		Assert::same(5, $this->uut->scroll(5));
+		Assert::same(10, $this->helper_fetchCurrentSingle());
+
+		Assert::same(989, $this->uut->scroll(989));
+		Assert::same(999, $this->helper_fetchCurrentSingle());
+
+		Assert::same(1, $this->uut->scroll(1));
+		Assert::same(1000, $this->helper_fetchCurrentSingle());
+
+		Assert::same(0, $this->uut->scroll(1));
+		Assert::null($this->helper_fetchCurrentSingle());
+
+		Assert::same(0, $this->uut->scroll(1));
+	}
+
+	public function test_givenFifthPosition_whenScrollZero_thenGetOne()
+	{
+		$this->uut->moveTo(5);
+		Assert::same(0, $this->uut->scroll(0));
+	}
+
+	public function test_givenFifthPosition_whenMoveToBeginning_thenGetForegoingNumberOfRows()
+	{
+		$this->uut->moveTo(5);
+
+		Assert::same(4, $this->uut->scroll(ICursor::FETCH_FOREGOING));
+	}
+
+	public function test_givenBeginningPosition_whenMoveToBeginning_thenGetZero()
+	{
+		Assert::same(0, $this->uut->scroll(ICursor::FETCH_FOREGOING));
+	}
 
 
 	// fetchOneAt()
diff --git a/tests/Cursor/PostgresCursorTest.phpt b/tests/Cursor/PostgresCursorTest.phpt
index 716babdf4e5b08fc114b0b526c76856c189fdb9a..97e8a0c4ff5a2600cad1925dabba2a3cfc5a00f0 100644
--- a/tests/Cursor/PostgresCursorTest.phpt
+++ b/tests/Cursor/PostgresCursorTest.phpt
@@ -3,11 +3,11 @@
  * @testCase
  */
 
-namespace Grifart\Mappi\Tests\Store\Cursor;
+namespace Grifart\Mappi\Tests\Cursor;
 
 use Dibi\DriverException;
+use Grifart\Mappi\Cursor\Driver\PostgresCursorFactory;
 use Grifart\Mappi\Cursor\ICursor;
-use Grifart\Mappi\Cursor\PostgresCursorFactory;
 use Tester\Environment;
 
 require_once __DIR__ . "/../bootstrap.php";
diff --git a/tests/Cursor/SemanticCursorIntegration.phpt b/tests/Cursor/SemanticCursorIntegration.phpt
index 5cef2c7d38778b9b5f46f377606268bcc5bc6cc1..eef253da3fd04e483a8c4b0de3f3d6ec446bd929 100644
--- a/tests/Cursor/SemanticCursorIntegration.phpt
+++ b/tests/Cursor/SemanticCursorIntegration.phpt
@@ -3,9 +3,8 @@
  * @testCase
  */
 
-namespace Grifart\Mappi\Tests\Store\Cursor;
+namespace Grifart\Mappi\Tests\Cursor;
 
-use Grifart\Mappi\Cursor\ArrayCursorDriver;
 use Grifart\Mappi\Cursor\Cursor;
 use Grifart\Mappi\Cursor\SemanticCursor;
 use Mockery;
@@ -17,7 +16,7 @@ require_once __DIR__ . "/ICursorTest.php";
  * Behavioral test for SemanticCursor using mocking.
  * @link http://docs.mockery.io/en/latest/
  *
- * @package Grifart\Mappi\Tests\Store\Cursor
+ * @package Grifart\Mappi\Tests\Cursor
  */
 class SemanticCursorIntegrationTest extends ICursorTest
 {
@@ -27,11 +26,7 @@ class SemanticCursorIntegrationTest extends ICursorTest
 
 	protected function setUp()
 	{
-//		global $connection, $SQL_thousandRowsAscending;
-//		$connection->begin();
-
-
-		$driver = new ArrayCursorDriver();
+		$driver = new Driver\ArrayCursorDriver();
 		$driver->createTestCursor("test", 1000);
 		$cursor = new Cursor(
 			$driver,
@@ -44,14 +39,6 @@ class SemanticCursorIntegrationTest extends ICursorTest
 		parent::setUp();
 	}
 
-	public function tearDown()
-	{
-//		global $connection;
-//		$connection->rollback();
-
-		parent::tearDown();
-	}
-
 }
 
 (new SemanticCursorIntegrationTest())->run();
diff --git a/tests/Cursor/SemanticCursorTest.phpt b/tests/Cursor/SemanticCursorTest.phpt
index 329f7edbc7574f5751f924f7acbdb2efe3dac227..7b05be6741bf8d817d2fa81481c8783c85bfe191 100644
--- a/tests/Cursor/SemanticCursorTest.phpt
+++ b/tests/Cursor/SemanticCursorTest.phpt
@@ -3,7 +3,7 @@
  * @testCase
  */
 
-namespace Grifart\Mappi\Tests\Store\Cursor;
+namespace Grifart\Mappi\Tests\Cursor;
 
 use Grifart\Mappi\Cursor\CursorException;
 use Grifart\Mappi\Cursor\ICursor;
@@ -19,7 +19,7 @@ require_once __DIR__ . "/ICursorTest.php";
  * Behavioral test for SemanticCursor using mocking.
  * @link http://docs.mockery.io/en/latest/
  *
- * @package Grifart\Mappi\Tests\Store\Cursor
+ * @package Grifart\Mappi\Tests\Cursor
  */
 class SemanticCursorTest extends BaseTest
 {
@@ -137,6 +137,22 @@ class SemanticCursorTest extends BaseTest
 		$this->uut->moveToEnd();
 	}
 
+	// ---- the extension - scroll* ----
+
+	public function test_scrollBeginning() {
+		$this->mockedCursor->shouldReceive("scroll")
+			->with(ICursor::FETCH_FOREGOING)->once()->andReturn(5);
+
+		Assert::same(5, $this->uut->scrollToBeginning());
+	}
+
+	public function test_scrollEnd() {
+		$this->mockedCursor->shouldReceive("scroll")
+			->with(ICursor::FETCH_REMAINING)->once()->andReturn(5);
+
+		Assert::same(5, $this->uut->scrollToEnd());
+	}
+
 	// ---- the extension - fetch* ----
 
 	public function test_fetchNext()
diff --git a/tests/Cursor/TrackedCursorTest.phpt b/tests/Cursor/TrackedCursorTest.phpt
index 0511173a71b4903bc7b204e7cacebf8b60e990b7..1e3e21bd644a8d077f8aa10388d814de92f13a33 100644
--- a/tests/Cursor/TrackedCursorTest.phpt
+++ b/tests/Cursor/TrackedCursorTest.phpt
@@ -3,14 +3,13 @@
  * @testCase
  */
 
-// TODO: moveBy() ->isOnRecord() ->moveBy() ->isOnRecord() (now I see state before and after command)
 // TODO: Maybe construct Cursor class in TrackedCursor constructor?
 
-namespace Grifart\Mappi\Tests\Store\Cursor;
+namespace Grifart\Mappi\Tests\Cursor;
 
-use Grifart\Mappi\Cursor\ArrayCursorDriver;
 use Grifart\Mappi\Cursor\Cursor;
-use Grifart\Mappi\Cursor\CursorPosition;
+use Grifart\Mappi\Cursor\ICursor;
+use Grifart\Mappi\Cursor\Position;
 use Grifart\Mappi\Cursor\TrackedCursor;
 use Tester\Assert;
 
@@ -19,19 +18,38 @@ require_once __DIR__ . "/ICursorTest.php";
 
 class TrackedCursorTest extends ICursorTest
 {
-	/** @link https://en.wikipedia.org/wiki/42_(number)#Hitchhiker.27s_Guide_to_the_Galaxy */
-	const THE_MAGIC_NUMBER = 42;
-
 	/** @var TrackedCursor */
 	protected $uut;
 
+	// test with underlying postgres driver
+//	protected function setUp()
+//	{
+//		global $connection, $SQL_thousandRowsAscending;
+//		try{
+//			$connection->begin();
+//
+//			$factory = new PostgresCursorFactory($connection);
+//			$cursor = $factory->create($SQL_thousandRowsAscending, true);
+//			$this->uut = new TrackedCursor($cursor, Position::fromLeft(0));
+//		} catch (DriverException $e) {
+//			Environment::skip("It looks like you haven't properly set-up dibi connection to PostgreSQL. Check tests/bootstrap.php");
+//		}
+//
+//		parent::setUp();
+//	}
+//
+//	public function tearDown()
+//	{
+//		global $connection;
+//		$connection->rollback();
+//
+//		parent::tearDown();
+//	}
+
 	protected function setUp()
 	{
-//		global $connection, $SQL_thousandRowsAscending;
-//		$connection->begin();
 
-//		$factory = new PostgresCursorFactory($connection);
-		$driver = new ArrayCursorDriver();
+		$driver = new Driver\ArrayCursorDriver();
 		$driver->createTestCursor("test", 1000);
 		$cursor = new Cursor(
 			$driver,
@@ -40,19 +58,11 @@ class TrackedCursorTest extends ICursorTest
 
 		$this->uut = new TrackedCursor(
 			$cursor,
-			CursorPosition::fromLeft(0)
+			Position::fromLeft(0)
 		);
 		parent::setUp();
 	}
 
-	public function tearDown()
-	{
-//		global $connection;
-//		$connection->rollback();
-
-		parent::tearDown();
-	}
-
 	private function assertPosition(string $position)
 	{
 		Assert::equal($position, (string)$this->uut->getPosition());
@@ -161,6 +171,81 @@ class TrackedCursorTest extends ICursorTest
 		$this->assertPosition("left:0");
 	}
 
+	public function test_getPosition_left_scroll()
+	{
+		Assert::same(0, $this->uut->scroll(-5));
+		$this->assertPosition("left:0");
+
+		Assert::same(0, $this->uut->scroll(0));
+		$this->assertPosition("left:0");
+
+		Assert::same(1, $this->uut->scroll(1));
+		$this->assertPosition("left:1");
+
+		Assert::same(0, $this->uut->scroll(0));
+		$this->assertPosition("left:1");
+
+		Assert::same(0, $this->uut->scroll(-1));
+		$this->assertPosition("left:0");
+
+		Assert::same(1, $this->uut->scroll(1));
+		$this->assertPosition("left:1");
+		
+		Assert::same(999, $this->uut->scroll(999));
+		Assert::same(1000, $this->helper_fetchCurrentSingle()); // regression
+		$this->assertPosition("left:1000");
+
+		Assert::same(0, $this->uut->scroll(0));
+		$this->assertPosition("left:1000");
+		
+		Assert::same(0, $this->uut->scroll(1));
+		$this->assertPosition("left:1001");
+
+		Assert::same(0, $this->uut->scroll(1));
+		$this->assertPosition("left:1001");
+
+		Assert::same(1, $this->uut->scroll(-1));
+		$this->assertPosition("left:1000");
+
+		Assert::same(0, $this->uut->scroll(1));
+		$this->assertPosition("left:1001");
+
+		Assert::same(1000, $this->uut->scroll(ICursor::FETCH_FOREGOING));
+		$this->assertPosition("left:0");
+
+		Assert::same(1000, $this->uut->scroll(ICursor::FETCH_REMAINING));
+		$this->assertPosition("left:1001");
+
+		Assert::same(1000, $this->uut->scroll(ICursor::FETCH_FOREGOING));
+		$this->assertPosition("left:0");
+	}
+	
+	public function test_getPosition_right_scroll()
+	{
+		$this->uut->moveFromEndTo(0);
+		$this->assertPosition("right:0");
+
+		$this->uut->scroll(1);
+		$this->assertPosition("right:0");
+
+		$this->uut->scroll(0);
+		$this->assertPosition("right:0");
+
+		$this->uut->scroll(-5);
+		$this->assertPosition("right:5");
+
+		$this->uut->scroll(-995);
+		$this->assertPosition("right:1000");
+		Assert::equal(1, $this->helper_fetchCurrentSingle()); // regression
+
+		$this->uut->scroll(-1);
+		$this->assertPosition("right:1001");
+		Assert::null($this->helper_fetchCurrentSingle()); // regression
+
+		$this->uut->scroll(-1);
+		$this->assertPosition("right:1001");
+	}
+
 	public function test_getPosition_left_fetchRange()
 	{
 		$this->assertPosition("left:0");