======
Cursor
======

::

    >>> from crate.client import connect

    >>> connection = connect(client=connection_client_mocked)
    >>> cursor = connection.cursor()

The rowcount and duration attribute is -1 in case no ``execute()`` has been performed on the cursor.

::

    >>> cursor.rowcount
    -1

    >>> cursor.duration
    -1

Hardcode the next response of the mocked connection client, so we won't need a sql statement
to execute::

    >>> connection.client.set_next_response({
    ...     "rows":[ [ "North West Ripple", 1 ], [ "Arkintoofle Minor", 3 ], [ "Alpha Centauri", 3 ] ],
    ...     "cols":[ "name", "position" ],
    ...     "rowcount":3,
    ...     "duration":123
    ... })

fetchone()
==========

Calling ``fetchone()`` on the cursor object the first time after an execute returns the first row::

    >>> cursor.execute('')

    >>> cursor.fetchone()
    ['North West Ripple', 1]

Each call to ``fetchone()`` increments the cursor and returns the next row::

    >>> cursor.fetchone()
    ['Arkintoofle Minor', 3]

One more iteration::

    >>> cursor.next()
    ['Alpha Centauri', 3]

The iteration is stopped after the last row is returned.
A further call to ``fetchone()`` returns an empty result::

    >>> cursor.fetchone()

Using ``fetchone()`` on a cursor before issuing a database statement results
in an error::

    >>> new_cursor = connection.cursor()
    >>> new_cursor.fetchone()
    Traceback (most recent call last):
    ...
    crate.client.exceptions.ProgrammingError: No result available. execute() or executemany() must be called first.


fetchmany()
===========

``fetchmany()`` takes an argument which specifies the number of rows we want to fetch::

    >>> cursor.execute('')

    >>> cursor.fetchmany(2)
    [['North West Ripple', 1], ['Arkintoofle Minor', 3]]

If the specified number of rows not being available, fewer rows may returned::

    >>> cursor.fetchmany(2)
    [['Alpha Centauri', 3]]

    >>> cursor.execute('')

If no number of rows are specified it defaults to the current cursor.arraysize::

    >>> cursor.arraysize
    1

    >>> cursor.fetchmany()
    [['North West Ripple', 1]]

    >>> cursor.execute('')
    >>> cursor.arraysize = 2
    >>> cursor.fetchmany()
    [['North West Ripple', 1], ['Arkintoofle Minor', 3]]

If zero number of rows are specified, all rows left are returned::

    >>> cursor.fetchmany(0)
    [['Alpha Centauri', 3]]

fetchall()
==========

``fetchall()`` fetches all (remaining) rows of a query result::

    >>> cursor.execute('')

    >>> cursor.fetchall()
    [['North West Ripple', 1], ['Arkintoofle Minor', 3], ['Alpha Centauri', 3]]

Since all data was fetched 'None' is returned by ``fetchone()``::

    >>> cursor.fetchone()

And each other call returns an empty sequence::

    >>> cursor.fetchmany(2)
    []

    >>> cursor.fetchmany()
    []

    >>> cursor.fetchall()
    []

iteration
=========

The cursor supports the iterator interface and can be iterated upon::

    >>> cursor.execute('')
    >>> [row for row in cursor]
    [['North West Ripple', 1], ['Arkintoofle Minor', 3], ['Alpha Centauri', 3]]

When no other call to execute has been done, it will raise StopIteration on
subsequent iterations::

    >>> next(cursor)
    Traceback (most recent call last):
    ...
    StopIteration

    >>> cursor.execute('')
    >>> for row in cursor:
    ...     row
    ['North West Ripple', 1]
    ['Arkintoofle Minor', 3]
    ['Alpha Centauri', 3]

Iterating over a new cursor without results will immediately raise a ProgrammingError::

    >>> new_cursor = connection.cursor()
    >>> next(new_cursor)
    Traceback (most recent call last):
    ...
    crate.client.exceptions.ProgrammingError: No result available. execute() or executemany() must be called first.

description
===========

::

    >>> cursor.description
    (('name', None, None, None, None, None, None), ('position', None, None, None, None, None, None))

rowcount
========

The ``rowcount`` property specifies the number of rows that the last ``execute()`` produced::

    >>> cursor.execute('')
    >>> cursor.rowcount
    3

The attribute is -1 in case the cursor has been closed::

    >>> cursor.close()
    >>> cursor.rowcount
    -1

If the last respsonse doesn't contain the rowcount attribute -1 is returned::

    >>> cursor = connection.cursor()
    >>> connection.client.set_next_response({
    ...     "rows":[],
    ...     "cols":[],
    ...     "duration":123
    ... })

    >>> cursor.execute('')
    >>> cursor.rowcount
    -1

    >>> connection.client.set_next_response({
    ...     "rows":[ [ "North West Ripple", 1 ], [ "Arkintoofle Minor", 3 ], [ "Alpha Centauri", 3 ] ],
    ...     "cols":[ "name", "position" ],
    ...     "rowcount":3,
    ...     "duration":123
    ... })

duration
========

The ``duration`` property specifies the server-side duration in milliseconds of the last query
issued by ``execute()``::

    >>> cursor = connection.cursor()
    >>> cursor.execute('')
    >>> cursor.duration
    123

The attribute is -1 in case the cursor has been closed::

    >>> cursor.close()
    >>> cursor.duration
    -1

    >>> connection.client.set_next_response({
    ...     "results": [
    ...         {
    ...             "rowcount": 3
    ...         },
    ...         {
    ...             "rowcount": 2
    ...         }
    ...     ],
    ...     "duration":123,
    ...     "cols":[ "name", "position" ],
    ... })

executemany
===========

``executemany()`` allows to execute a single sql statement against a sequence
of parameters::

    >>> cursor = connection.cursor()

    >>> cursor.executemany('', (1,2,3))
    [{'rowcount': 3}, {'rowcount': 2}]

    >>> cursor.rowcount
    5
    >>> cursor.duration
    123

``executemany()`` is not intended to be used with statements returning result
sets. The result will always be empty::

    >>> cursor.fetchall()
    []

For completeness' sake the cursor description is updated nonetheless::

    >>> [ desc[0] for desc in cursor.description ]
    ['name', 'position']

    >>> connection.client.set_next_response({
    ...     "rows":[ [ "North West Ripple", 1 ], [ "Arkintoofle Minor", 3 ], [ "Alpha Centauri", 3 ] ],
    ...     "cols":[ "name", "position" ],
    ...     "rowcount":3,
    ...     "duration":123
    ... })


close()
=======

After closing a cursor the connection will be unusable. If any operation is attempted with the
closed connection an ``ProgrammingError`` exception will be raised::

    >>> cursor = connection.cursor()
    >>> cursor.execute('')
    >>> cursor.fetchone()
    ['North West Ripple', 1]

    >>> cursor.close()
    >>> cursor.fetchone()
    Traceback (most recent call last):
    ...
    crate.client.exceptions.ProgrammingError: Cursor closed

    >>> cursor.fetchmany()
    Traceback (most recent call last):
    ...
    crate.client.exceptions.ProgrammingError: Cursor closed

    >>> cursor.fetchall()
    Traceback (most recent call last):
    ...
    crate.client.exceptions.ProgrammingError: Cursor closed

    >>> cursor.next()
    Traceback (most recent call last):
    ...
    crate.client.exceptions.ProgrammingError: Cursor closed

.. Hidden: close connection

    >>> connection.close()
