/* KInterbasDB Python Package - Implementation of Cursor
 *
 * Version 3.3
 *
 * The following contributors hold Copyright (C) over their respective
 * portions of code (see license.txt for details):
 *
 * [Original Author (maintained through version 2.0-0.3.1):]
 *   1998-2001 [alex]  Alexander Kuznetsov   <alexan@users.sourceforge.net>
 * [Maintainers (after version 2.0-0.3.1):]
 *   2001-2002 [maz]   Marek Isalski         <kinterbasdb@maz.nu>
 *   2002-2007 [dsr]   David Rushby          <woodsplitter@rocketmail.com>
 * [Contributors:]
 *   2001      [eac]   Evgeny A. Cherkashin  <eugeneai@icc.ru>
 *   2001-2002 [janez] Janez Jere            <janez.jere@void.si>
 */

static int CursorTracker_add(CursorTracker **list_slot, Cursor *cont);
static int CursorTracker_remove(CursorTracker **list_slot, Cursor *cont,
    boolean
  );

/****************** "PRIVATE" DECLARATIONS:BEGIN *******************/
PyObject *cursor_support__empty_tuple;

PyObject *cursor_support__method_name__fetchonetuple;
PyObject *cursor_support__method_name__fetchonemap;
/****************** "PRIVATE" DECLARATIONS:END *******************/

/**************** Cursor METHODS INACCESSIBLE TO PYTHON:BEGIN ****************/

static PyObject *pyob_Cursor_new(
    PyTypeObject *subtype, PyObject *args, PyObject *kwargs
  )
{
  Cursor *self;

  self = (Cursor *) subtype->tp_alloc(subtype, 0);
  if (self == NULL) { goto fail; }

  /* Nullify all of the self's fields first, so that if one of the field
   * initializations that requires additional allocation fails, the cleanup
   * code can check each field without fear of referring to uninitialized
   * memory. */

  self->state = CURSOR_STATE_CREATED;
  self->trans = NULL;
  self->con_python_wrapper = NULL;

  self->ps_current = NULL;
  PSCACHE_NULLIFY(&self->ps_cache_internal);
  self->ps_tracker = NULL;

  self->name = NULL;

  self->arraysize = PYTHONDBAPI_DEFAULT_ARRAYSIZE;

  self->objects_to_release_after_execute = NULL;

  self->exec_proc_results = NULL;
  self->last_fetch_status = NO_FETCH_ATTEMPTED_YET;

  self->type_trans_in = NULL;
  self->type_trans_out = NULL;
  self->output_type_trans_return_type_dict = NULL;

  return (PyObject *) self;

  fail:
    /* Lack of assert (PyErr_Occurred()) here is deliberate. */
    Py_XDECREF(self);
    return NULL;
} /* pyob_Cursor_new */

static int Cursor_init(Cursor *self, PyObject *args, PyObject *kwargs) {
  assert (self->state == CURSOR_STATE_CREATED);

  { /* Extract and validate arguments: */
    PyObject *trans_ = NULL;

    if (!PyArg_ParseTuple(args, "O", &trans_)) { goto fail; }

    if (PyObject_IsInstance(trans_, (PyObject *) &TransactionType)) {
      Py_INCREF(trans_); /* Maintain ref-count symmetry with other branch. */
    } else {
      /* trans_ is not a Transaction, so assume it's a kinterbasdb.Connection,
       * and try to receive its main transaction.  That that we use property
       * name _main_trans rather than main_transaction, because we want to get
       * a Transaction instance, not an instance of
       * ExternallyVisibleMainTransaction. */
      PyObject *mt = PyObject_GetAttr(trans_, shared___s__main_trans);
      if (mt == NULL) {
        Py_INCREF(trans_); /* Maintain ref-count symmetry with other branch. */
      } else {
        trans_ = mt;
        /* trans_ now contains a new reference, so these two branches have
         * ref-count symmetry. */
      }
      /* At this point, trans_ contains an owned reference, whether it's an
       * instance of Transaction or not. */
      if (!PyObject_IsInstance(trans_, (PyObject *) &TransactionType)) {
        Py_DECREF(trans_);
        raise_exception(ProgrammingError, "First argument to Cursor constructor"
            " must be either Transaction or Connection instance."
          );
        goto fail;
      }
    }

    assert (trans_ != NULL);
    assert (PyObject_IsInstance(trans_, (PyObject *) &TransactionType));
    TRANS_REQUIRE_OPEN_((Transaction *) trans_,
        /* Failure actions: */
        Py_DECREF(trans_);
        goto fail
      );

    /* We assign without INCREF because we're merely transferring reference
     * ownership from trans_ to self->trans: */
    self->trans = (Transaction *) trans_;

    /* TAG:TRANSACTION_SUBORDINATE_OBJECT_SURVIVAL_BYPASS: */
    /* If (Transaction_is_main(self->trans)), the Transaction will not own a
     * reference to its connection, so we always create an artificial
     * reference, to ensure that the connection will remain alive at least as
     * long as this Cursor does. */
    self->con_python_wrapper = Transaction_get_con_python_wrapper(self->trans);
    assert (self->con_python_wrapper != NULL);
    Py_INCREF(self->con_python_wrapper);
  } /* End of argument extraction and validation block. */

  assert (self->trans != NULL);
  assert (PyObject_IsInstance(
      (PyObject *) self->trans, (PyObject *) &TransactionType
    ));

  assert (Transaction_get_con(self->trans) != NULL);
  CON_ACTIVATE(Transaction_get_con(self->trans), goto fail);

  if (   PSCache_initialize(&self->ps_cache_internal, PREP_STMT_CACHE_CAPACITY)
      != 0
     )
  { goto fail_with_passivation; }

  self->objects_to_release_after_execute = PyList_New(0);
  if (self->objects_to_release_after_execute == NULL) {
    goto fail_with_passivation;
  }

  /* Enter self in the transaction's open cursor tracker: */
  if (CursorTracker_add(&self->trans->open_cursors, self) != 0) {
    goto fail_with_passivation;
  }

  self->state = CURSOR_STATE_OPEN;
  CON_PASSIVATE(Transaction_get_con(self->trans));
  return 0;

  fail_with_passivation:
    assert (PyErr_Occurred());

    assert (self->trans != NULL);
    assert (Transaction_get_con(self->trans) != NULL);
    CON_PASSIVATE(Transaction_get_con(self->trans));

    /* Fall through to fail: */
  fail:
    assert (PyErr_Occurred());
    return -1;
} /* Cursor_init */

static void Cursor_delete(Cursor *self) {
  assert (self->trans == NULL);

  assert (self->ps_current == NULL);
  assert (PSCache_has_been_deleted(&self->ps_cache_internal));
  assert (self->ps_tracker == NULL);

  assert (self->name == NULL);

  Py_XDECREF(self->objects_to_release_after_execute);

  assert (self->exec_proc_results == NULL);

  Py_XDECREF(self->type_trans_in);
  Py_XDECREF(self->type_trans_out);

  Py_XDECREF(self->output_type_trans_return_type_dict);
} /* Cursor_delete */

static int Cursor_clear(Cursor *self, boolean allowed_to_raise) {
  if (self->exec_proc_results != NULL) {
    /* This block will only be reached if the client executed a result-
     * returning stored procedure with the EXECUTE PROCEDURE statement, but the
     * client never retrieved the results. */
    Py_DECREF(self->exec_proc_results);
    self->exec_proc_results = NULL;
  }

  if (self->name != NULL) {
    Py_DECREF(self->name);
    self->name = NULL;
  }

  /* Clear the fetch status flag because we might be about to deal with a new
   * result set.  In any case, we will never again fetch from the old result
   * set (if any). */
  self->last_fetch_status = NO_FETCH_ATTEMPTED_YET;

  self->state = CURSOR_STATE_CLOSED;

  return 0;

  /* Reactivate if a failure-prone operation is introduced:
  fail:
    if (allowed_to_raise) {
      assert (PyErr_Occurred());
      return -1;
    } else {
      SUPPRESS_EXCEPTION;
      return 0;
    }
  */
} /* Cursor_clear */

static int Cursor_clear_for_another_execution(Cursor *self) {
  if (self->ps_current != NULL) {
    /* If the most recently executed statement is of a type that might still
     * have an open result set, tell the server to release that result set, but
     * to keep other resources open in case there are future executions of the
     * statement.
     *
     * Note that we only "isc_close" self->ps_current here; we do not set
     * self->ps_current to NULL. */
    const int statement_type = self->ps_current->statement_type;

    assert (self->ps_current->cur != NULL);
    assert (self->ps_current->cur == self);
    assert (self->trans != NULL);

    if (   Transaction_stats_n_executed_since_phys_start(self->trans) > 0
        && (
               self->name != NULL
            ||
               /* Any of the following statement types might've opened result
                * sets: */
              (
                (   statement_type == isc_info_sql_stmt_select
                 || statement_type == isc_info_sql_stmt_select_for_upd
                 /* Even though isc_info_sql_stmt_exec_procedure can
                  * conceptually return a row of results, we exclude it here,
                  * because at the Firebird API level, we exhaust the result
                  * set immediately, then virtualize the Python DB API fetch,
                  * rather than actually executing an isc_dsql_fetch after a
                  * statement of type isc_info_sql_stmt_exec_procedure.  This
                  * means that
                  *   isc_dsql_free_statement(..., DSQL_close)
                  * does not need to be performed here, because there cannot be
                  * a lingering, open result set. */
                )
                /* If the result set was exhausted during fetch operations, the
                 * statement handle should have been "DSQL_close"d at that
                 * time: */
                && self->last_fetch_status != RESULT_SET_EXHAUSTED
              )
           )
       )
    {
      if (PreparedStatement_isc_close(self->ps_current, TRUE) != 0) {
        goto fail;
      }
    }
  }

  if (Cursor_clear(self, TRUE) != 0) { goto fail; }

  return 0;

  fail:
    assert (PyErr_Occurred());
    return -1;
} /* Cursor_clear_for_another_execution */

CConnection *Cursor_get_con(Cursor *cur) {
  assert (cur != NULL);
  if (cur->trans == NULL) {
    return NULL;
  } else {
    return Transaction_get_con(cur->trans);
  }
} /* Cursor_get_con */

static int _Cursor_require_open(Cursor *self, char *failure_message) {
  /* If self is not an open cursor, raises the supplied error message (or a
   * default if no error message is supplied).
   * Returns 0 if the cursor was open; -1 if it was closed. */
  assert (self != NULL);
  {
    CConnection *con = Cursor_get_con(self);
    char *conn_failure_message = "Invalid cursor state.  The connection"
      " associated with this cursor is not open, and therefore the cursor"
      " should not be open either.";
    if (   con != NULL
        && Connection_require_open(con, conn_failure_message) == 0
        && self->state == CURSOR_STATE_OPEN
       )
    { return 0; }
  }

  if (failure_message == NULL) {
    failure_message = "Invalid cursor state.  The cursor must be open to"
      " perform this operation.";
  }
  raise_exception(ProgrammingError, failure_message);
  return -1;
} /* _Cursor_require_open */

#define CUR_ENSURE_TRANSACTION(cursor) \
  CUR_ENSURE_TRANSACTION_WITH_FAILURE(cursor, return NULL);

#define CUR_ENSURE_TRANSACTION_WITH_FAILURE(cursor, failure_action) \
  { \
    int status; \
    assert (cursor != NULL); \
    assert ((cursor)->ob_refcnt >= 1); \
    status = _Cursor_require_open(cursor, NULL); \
    if (status == 0) { \
      assert ((cursor)->trans != NULL); \
      status = Transaction_ensure_active((cursor)->trans, NULL); \
    } \
    if (status == 0) { \
      assert (Transaction_is_active((cursor)->trans)); \
    } else { \
      failure_action; \
    } \
  }

static void Cursor_clear_superior_references(Cursor *self) {
  assert (self != NULL);
  assert (self->trans != NULL);
  assert (self->con_python_wrapper != NULL);

  Py_DECREF(self->trans);
  self->trans = NULL;
  /* Must DECREF the kinterbasdb.Connection *after* the Transaction. */
  Py_DECREF(self->con_python_wrapper);
  self->con_python_wrapper = NULL;
} /* Cursor_clear_superior_references */

static int Cursor_ensure_PSCache(Cursor *self) {
  PSCache *ps_cache = &self->ps_cache_internal;

  if (PSCache_has_been_deleted(ps_cache)) {
    /* The connection that hosts self->cursor must have timed out at some point
     * and caused the cursor to flush its PSCache. */
    if (PSCache_initialize(ps_cache, PREP_STMT_CACHE_CAPACITY) != 0) {
      return -1;
    }
    assert (!PSCache_has_been_deleted(ps_cache));
  }

  return 0;
} /* Cursor_ensure_PSCache */

static int Cursor_close_prepared_statements(Cursor *self,
    const boolean allowed_to_raise,
    const boolean clear_ps_superior_refs
  )
{
  int status = 0;

  if (self->ps_current != NULL) {
    /* If self->ps_current is for internal use, there should be *exactly one*
     * owned reference to it, within self->ps_cache_internal->container. */
    assert (self->ps_current->for_internal_use ?
          self->ps_current->ob_refcnt == 1
        : TRUE
      );
    /* The lack of Py_DECREF here is deliberate, because self->ps_current never
     * (conceptually) contains an owned reference. */
    self->ps_current = NULL;
  }

  {
    PSCache *psc = &self->ps_cache_internal;
    if (!PSCache_has_been_deleted(psc)) {
      PSCache_delete(psc);
      assert (PSCache_has_been_deleted(psc));
    }
  }

  if (self->ps_tracker != NULL) {
    if (clear_ps_superior_refs) {
      if (PSTracker_release(&self->ps_tracker) == 0) {
        assert (self->ps_tracker == NULL);
      } else {
        if (allowed_to_raise) {
          goto fail;
        } else {
          status = -1;
          SUPPRESS_EXCEPTION;
        }
      }
    } else {
      #ifndef NDEBUG
        const Py_ssize_t orig_cur_refcnt = self->ob_refcnt;
      #endif
      PSTracker *ps_node = self->ps_tracker;

      while (ps_node != NULL) {
        PreparedStatement *ps = ps_node->contained;
        assert (ps != NULL);

        if(PreparedStatement_untrack_with_superior_ref_clear_control
            (ps, allowed_to_raise, FALSE /* <-- Don't clear sup. refs. */) != 0
          )
        {
          if (allowed_to_raise) {
            goto fail;
          } else {
            status = -1;
            SUPPRESS_EXCEPTION;
          }
        }

        ps_node = ps_node->next;
      }

      /* Free the memory of the PreparedStatement tracker's nodes, since all of
       * PreparedStatements there are now closed.  Note that this operation
       * only frees the memory of the *linked list nodes*--it does not free the
       * *PreparedStatement objects themselves*. */
      {
        PSTracker *ps_node = self->ps_tracker;
        while (ps_node != NULL) {
          PSTracker *ps_node_next = ps_node->next;
          kimem_main_free(ps_node);
          ps_node = ps_node_next;
        }
        self->ps_tracker = NULL;
      }

      /* Since we ordered each PreparedStatement not to clear its superior
       * reference(s), and since the GIL is supposed to remain held by the CTT
       * throughout the entire Connection timeout process, the Cursor's
       * reference count should not have changed: */
      assert (self->ob_refcnt == orig_cur_refcnt);
    }
  }
  /* Whether by the conventional means, or by close-PreparedStatements-but-do-
   * not-clear-their-superior-references, self->ps_tracker should have been
   * released. */
  assert (self->ps_tracker == NULL);

  return status;

  fail:
    assert (PyErr_Occurred());
    return -1;
} /* Cursor_close_prepared_statements */

static int Cursor_close_without_unlink(Cursor *self, boolean allowed_to_raise) {
  /* The "without unlink" part of this function's name refers to not unlinking
   * Cursor self from self's Transaction, rather than to not unlinking self's
   * dependent PreparedStatements from self. */
  int status = 0;

  if (Cursor_clear(self, allowed_to_raise) == 0) {
    assert (self->state == CURSOR_STATE_CLOSED);
  } else {
    if (allowed_to_raise) {
      goto fail;
    } else {
      status = -1;
      SUPPRESS_EXCEPTION;
    }
  }

  if (Cursor_close_prepared_statements(self, allowed_to_raise, TRUE) != 0) {
    if (allowed_to_raise) {
      goto fail;
    } else {
      status = -1;
      SUPPRESS_EXCEPTION;
    }
  }

  return status;

  fail:
    assert (PyErr_Occurred());
    return -1;
} /* Cursor_close_without_unlink */

#define CUR_HAS_BEEN_UNTRACKED(cur) ((cur)->trans == NULL)

static int Cursor_untrack(Cursor *self, boolean allowed_to_raise) {
  /* We're here because the superior object (a Transaction) ordered a purge of
   * its tracker.
   * Since self might have subordinate objects (PreparedStatements) that will
   * release their references to their superior object (self), we must ensure
   * that if self becomes eligible for destruction as a result of this
   * untracking operation, self remains alive at least long enough to complete
   * the untracking in an orderly manner.
   * So, note the artifical INCREF(self)/DECREF(self) in this method. */
  int status = -1;

  assert (self != NULL);
  assert (self->trans != NULL);

  Py_INCREF(self);

  if (Cursor_close_without_unlink(self, allowed_to_raise) != 0) { goto fail; }

  Cursor_clear_superior_references(self);
  assert (self->trans == NULL);

  self->state = CURSOR_STATE_DROPPED;

  assert (CUR_HAS_BEEN_UNTRACKED(self));
  assert (!PyErr_Occurred());
  status = 0;
  goto clean;
  fail:
    assert (PyErr_Occurred());
    assert (status == -1);
    /* Fall through to clean: */
  clean:
    Py_DECREF(self);
    return status;
} /* Cursor_untrack */

static int Cursor_close_with_unlink(Cursor *self, boolean allowed_to_raise) {
  assert (self->trans != NULL);
  assert (self->trans->open_cursors != NULL);

  if (Cursor_close_without_unlink(self, allowed_to_raise) == 0) {
    assert (self->state == CURSOR_STATE_CLOSED);
  } else {
    if (allowed_to_raise) {
      goto fail;
    }
  }

  /* Remove self from the Transaction's open cursor tracker: */
  if (CursorTracker_remove(&self->trans->open_cursors, self, TRUE) != 0) {
    if (allowed_to_raise) {
      goto fail;
    } else {
      SUPPRESS_EXCEPTION;
    }
  }

  Cursor_clear_superior_references(self);
  self->state = CURSOR_STATE_DROPPED;

  return 0;

  fail:
    assert (PyErr_Occurred());
    return -1;
} /* Cursor_close_with_unlink */

static void Cursor_clear_and_leave_open(Cursor *self) {
  Cursor_clear(self, FALSE);
  self->state = CURSOR_STATE_OPEN;
} /* Cursor_clear_and_leave_open */

#define Cursor_recover_from_error Cursor_clear_and_leave_open

static PreparedStatement *Cursor_prepare_statement(Cursor *self,
    PyObject *sql, boolean for_internal_use
  )
{
  /* On the basis of Cursor self and str/unicode sql, this function returns a
   * new reference to a PreparedStatement that corresponds to sql, or NULL on
   * error.
   * This function also sets self->ps_current to NULL if it begins the
   * process of actually preparing the statement, whether or not the
   * preparation process succeeds.
   *
   * NOTES:
   *   Internal PSes do not own a reference to their Cursor; instead, their
   * Cursor owns a reference to each of them (accessible via the Cursor's
   * ps_cache_internal member).  The Cursor clears ps_cache_internal when it's
   * closed; internal PSes never initiate their own closure.
   *   Non-internal PSes own a reference to their Cursor, and their Cursor
   * never stores an owned reference to them.  Non-internal PSes are tracked in
   * Cursor member ps_tracker, which of course does not own any references to
   * its elements.
   *   The Python garbage collector will initiate the destruction of a
   * non-internal PS if its reference count falls to zero, but a PS's Cursor
   * will "prematurely" ask the non-internal PS to close itself if the Cursor's
   * Transaction asks the Cursor to "prematurely" close itself.  This is safe
   * because each non-internal PS owns a reference to its Cursor, so the Cursor
   * is guaranted to outlive any dependent PS.
   *   Note that the ps_current member of Cursor only ever holds a
   * (conceptually) borrowed reference or NULL--never an owned reference.
   *
   * RATIONALE:
   *   The major point of all these contortions is to avoid creating circular
   * references.  Python's garbage collector has a facility for dealing with
   * circular references, which can be enabled (on a per-class basis) for
   * classes written in C, but it does not call the __del__ methods of involved
   * objects under some circumstances because there are insurmountable
   * stability risks in doing so.
   *   Most of the classes in kinterbasdb (including both Cursor and
   * PreparedStatement) manage some underlying external resource, so it is
   * imperative that they be destroyed in an orderly and complete manner.  By
   * carefully arranging these circular references to act like non-circular
   * references, we can preserve the deterministic finalization that CPython
   * programmers expect, without requiring them to perform any explicit closure
   * of the resources. */
  PreparedStatement *ps = NULL;
  PyObject *sql_as_pystr = NULL;

  /* No cleanup of ps is included in _Cursor_prepare_statement_CLEANUP because
   * ps must either be NULL on error, or non-NULL and returned on success. */
  #define _Cursor_prepare_statement_CLEANUP \
    if (sql_as_pystr != NULL) { \
      Py_DECREF(sql_as_pystr); \
      sql_as_pystr = NULL; \
    }

  assert (sql != NULL);

  if (PyString_CheckExact(sql)) {
    Py_INCREF(sql); /* INCREF for symmetry with the unicode case. */
    sql_as_pystr = sql;
  } else if (PyUnicode_CheckExact(sql)) {
    #ifdef XXX_OBSOLETE_AND_INCORRECT
      /* Ideally, we would pass the database API the incoming SQL statement
       * without converting it to ASCII (i.e., via PyUnicode_AsWideChar or
       * something similar), but the database API doesn't accept anything but
       * ASCII. */

      /* PyUnicode_AsASCIIString creates a *new* PyStringObject, but will fail if
       * the contents of the unicode object can't be represented in ASCII. */
      sql_as_pystr = PyUnicode_AsASCIIString(sql);
      if (sql_as_pystr == NULL) {
        assert (PyErr_Occurred());
        goto fail;
      }
    #endif /* XXX_OBSOLETE_AND_INCORRECT */

    /* Try to convert the unicode SQL statement to a Python str instance in the
     * connection's encoding.  Note that although it'll be a str instance, it
     * might not be ASCII. */
    {
      PyObject *dbCSName = NULL;
      PyObject *ttu = NULL;
      PyObject *dbCS2PyCSMap = NULL;
      PyObject *pyCSName = NULL;

      assert (Cursor_get_con(self) != NULL);
      assert (Cursor_get_con(self)->python_wrapper_obj != NULL);
      dbCSName = PyObject_GetAttr(Cursor_get_con(self)->python_wrapper_obj,
          shared___s_charset
        );
      if (dbCSName == NULL) { goto sql_uniconv_clean; }

      /* If the client programmer didn't specify a character set when the
       * connection was created, we default to ASCII: */
      if (dbCSName == Py_None) {
        Py_DECREF(dbCSName);
        dbCSName = NULL;
        sql_as_pystr = PyUnicode_AsASCIIString(sql);
        /* Don't check sql_as_pystr; drop into generic handler if error. */
      } else {
        /* Import the kinterbasdb.typeconv_text_unicode module: */
        ttu = PyImport_ImportModule("kinterbasdb.typeconv_text_unicode");
        if (ttu == NULL) { goto sql_uniconv_clean; }

        dbCS2PyCSMap = PyObject_GetAttr(ttu,
            shared___s_DB_CHAR_SET_NAME_TO_PYTHON_ENCODING_MAP
          );
        if (dbCS2PyCSMap == NULL) { goto sql_uniconv_clean; }

        pyCSName = PyObject_GetItem(dbCS2PyCSMap, dbCSName);
        if (pyCSName == NULL || !PyString_CheckExact(pyCSName)) {
          /* YYY: Since it's possible for users to register custom character
           * sets, we should theoretically perform this lookup dynamically, by
           * consulting the FB system tables. */
          raise_exception(InternalError, "Attempt to look up Python codec name"
              " on basis of database char set name failed."
            );
          goto sql_uniconv_clean;
        }

        sql_as_pystr = PyUnicode_AsEncodedString(sql,
            PyString_AS_STRING(pyCSName),
            PyString_AS_STRING(shared___s_strict)
          );
        /* Don't check sql_as_pystr; drop into generic handler if error. */
      }

      sql_uniconv_clean:
        Py_XDECREF(dbCSName);
        Py_XDECREF(ttu);
        Py_XDECREF(dbCS2PyCSMap);
        Py_XDECREF(pyCSName);
        if (PyErr_Occurred()) { goto fail; }
    }
  } else {
    /* The lack of Py_DECREF here is deliberate, because self->ps_current
     * never (conceptually) contains an owned reference. */
    self->ps_current = NULL;

    raise_exception(InterfaceError, "SQL must be str or unicode object.");
    goto fail;
  }
  assert (sql_as_pystr != NULL);
  assert (PyString_CheckExact(sql_as_pystr));

  /* When running in a multithreaded environment, the database client library
   * seems to malfunction when asked to prepare the empty string, eventually
   * even failing to reject the preparation attempt!  It does not suffer the
   * same problem when asked to prepare a string that consists only of
   * spaces. */
  if (PyString_GET_SIZE(sql_as_pystr) == 0) {
    raise_exception(ProgrammingError, "Cannot prepare empty SQL statement.");
    goto fail;
  }

  if (!for_internal_use) {
    /* Non-internal PreparedStatements are only reused if the client programmer
     * explicitly passes them as the first parameter to Cursor.execute.  If
     * that had been the case, we wouldn't even be executing the
     * Cursor_prepare_statement function right now. */

    /* The lack of Py_DECREF here is deliberate, because self->ps_current
     * never (conceptually) contains an owned reference. */
    self->ps_current = NULL;
  } else if (self->ps_current != NULL) {
    assert (self->ps_current->sql != NULL);
    if (PreparedStatement_matches_sql(self->ps_current, sql_as_pystr)) {
      /* There's no need to add self->ps_current to one of the trackers; it's
       * certain to be in self->ps_cache_internal already. */
      ps = self->ps_current;
      /* The lack of Py_DECREF here is deliberate, because self->ps_current
       * never (conceptually) contains an owned reference. */
      self->ps_current = NULL;
      goto cache_hit;
    } else {
      /* The lack of Py_DECREF here is deliberate, because self->ps_current
       * never (conceptually) contains an owned reference. */
      self->ps_current = NULL;

      /* The previous statement (if any) wasn't the same as the one about to be
       * executed, but there might still be an appropriate PreparedStatement
       * somewhere in the cursor's internal cache. */
      ps = PSCache_find_prep_stmt_for_sql(&self->ps_cache_internal,
          sql_as_pystr
        );
      if (ps != NULL) { goto cache_hit; }
    }
  }

  /* cache_miss: */
    assert (ps == NULL);

    /* Create and initialize a fresh PS. */
    ps = PreparedStatement_create(self, for_internal_use);
    if (ps == NULL) {
      assert (PyErr_Occurred());
      goto fail;
    }

    if (PreparedStatement_open(ps, self, sql_as_pystr) != 0) {
      assert (PyErr_Occurred());
      goto fail_with_ps_free;
    }

    /* The PreparedStatement has now been created and initialized successfully.
     * Add it to either the internal-PS cache or the non-internal-PS
     * tracker: */
    {
      int track_result;
      if (for_internal_use) {
        track_result = PSCache_append(&self->ps_cache_internal, ps);
      } else {
        /* Note that the non-internal PreparedStatement tracker does not own
         * references to its elements.  Instead, each element removes itself
         * from the tracker if it dies before the Cursor does. */
        track_result = PSTracker_add(&self->ps_tracker, ps);
      }
      if (track_result != 0) {
        assert (PyErr_Occurred());
        goto fail_with_ps_free;
      }
    }

    goto succeed;

  cache_hit:
    assert (ps != NULL);
    /* Non-internal PreparedStatements are not cached within the Cursor, so a
     * request for one couldn't possibly result in a cache hit. */
    assert (for_internal_use);

    /* This functions must return a new reference; create one from the existing
     * object. */
    Py_INCREF(ps);
    /* Fall through to succeed: */

  succeed:
    assert (!PyErr_Occurred());
    _Cursor_prepare_statement_CLEANUP;

    assert (self->ps_current == NULL);

    self->state = CURSOR_STATE_OPEN;
    return ps;

  { /* (Scope for failure handlers.) */
    PyObject *ex_type;
    PyObject *ex_value;
    PyObject *ex_traceback;

    fail_with_ps_free:
      assert (PyErr_Occurred());

      PyErr_Fetch(&ex_type, &ex_value, &ex_traceback);

      assert (ps != NULL);
      Py_DECREF(ps);
      ps = NULL;

      PyErr_Restore(ex_type, ex_value, ex_traceback);

      assert (PyErr_Occurred());
      /* Fall through to fail: */
    fail:
      assert (PyErr_Occurred());

      _Cursor_prepare_statement_CLEANUP;

      assert (ps == NULL);
      assert (self->ps_current == NULL);

      self->state = CURSOR_STATE_CLOSED;
      return NULL;
  }
} /* Cursor_prepare_statement */

static PyObject *Cursor_execute(Cursor *self, PyObject *sql, PyObject *params)
{
  PreparedStatement *ps = NULL;
  ISC_STATUS *sv = NULL;

  assert (self != NULL);

  CUR_ENSURE_TRANSACTION(self);

  sv = self->status_vector;

  #ifdef ENABLE_CONNECTION_TIMEOUT
    assert (
        Connection_timeout_enabled(Transaction_get_con(self->trans))
      ? Transaction_get_con(self->trans)->state == CONOP_ACTIVE
      : TRUE
    );
  #endif

  if (Cursor_clear_for_another_execution(self) != 0) {
    goto fail_but_skip_cursor_dynmem_cleanup;
  }
  assert (self->state == CURSOR_STATE_CLOSED);

  /* For $params, accept any sequence except a basestring. */
  if (   PyString_Check(params) || PyUnicode_Check(params)
      || !PySequence_Check(params)
     )
  {
    raise_exception(InterfaceError,
        "Input parameter container must be a non-string sequence."
      );
    goto fail_but_skip_cursor_dynmem_cleanup;
  }

  if (sql == Py_None) {
    /* If cur.execute(None, ...), recall the last statement. */
    ps = self->ps_current;
    if (ps == NULL) {
      raise_exception(ProgrammingError, "No statement has previously been"
          " prepared or executed on this cursor."
        );
      goto fail_but_skip_cursor_dynmem_cleanup;
    }
    self->state = CURSOR_STATE_OPEN;
  } else if (PyObject_TypeCheck(sql, &PreparedStatementType)) {
    ps = (PreparedStatement *) sql;
    if (ps->cur != self) {
      raise_exception(ProgrammingError, "A PreparedStatement can only be used"
          " with the Cursor that originally prepared it."
        );
      goto fail_but_skip_cursor_dynmem_cleanup;
    }

    /* If there is a previous PreparedStatement, and ps is not it,
     * self->ps_current should be cleared: */
    if (self->ps_current != NULL && self->ps_current != ps) {
      /* The lack of Py_DECREF here for the previous contents of
       * self->ps_current is deliberate, because self->ps_current never
       * (conceptually) contains an owned reference. */
      self->ps_current = NULL;
    }

    self->state = CURSOR_STATE_OPEN;
  } else {
    ps = Cursor_prepare_statement(self, sql, TRUE);
    if (ps == NULL) { goto fail_but_skip_cursor_dynmem_cleanup; }
    /* We directed Cursor_prepare_statement to prepare for us an internal
     * statement.  When preparing an internal statement, that function returns
     * a new reference only for symmetry with the non-internal-PS case, so we
     * don't need to retain the new reference. */
    assert (ps->ob_refcnt == 2);
    Py_DECREF(ps);
    assert (ps->ob_refcnt == 1);
    assert (self->ps_current == NULL);
  }

  assert (self->state == CURSOR_STATE_OPEN);
  if (self->ps_current == NULL) {
    self->ps_current = ps;
  } else {
    /* We're re-executing the same statement: */
    assert (self->ps_current == ps);
  }

  /* Convert the Python input arguments to their XSQLVAR equivalents. */
  if (convert_input_parameters(self, params) < 0) { goto fail; }

  /* The fact that there are output fields does not guarantee that the
   * statement is compatible with a standard SELECT-fetch approach; it could be
   * an 'EXECUTE ... [RETURNING_VALUES ...]' on a stored procedure with output
   * params.
   *
   * Special case:
   *   The statement type is isc_info_sql_stmt_exec_procedure and it has at
   *   least one output parameter.
   *
   *   Unlike a SELECT statement whose target is a stored procedure, an
   *   EXECUTE PROCEDURE statement cannot return more than one row.  Such a
   *   procedure must be executed with isc_dsql_execute2, which returns its
   *   results immediately and is therefore not compatible with a standard
   *   execute-fetch approach.
   *
   *   However, we impose the "appearance of standard execute-fetch" on this
   *   special case by caching the results in a field of the cursor and
   *   returning the ONE cached row if/when fetch is called.  This behavior is
   *   required to comply with the Python DB API spec.
   *
   *   kinterbasdb up to and including version 2.0-0.3.1 would choke with an
   *   exception instead of behaving in the standard way.  It did so because
   *   it tried to execute the procedure in this special case with
   *   isc_dsql_execute rather than isc_dsql_execute2.  That appeared to work
   *   fine during the execution step, but gagged if/when fetch was
   *   subsequently called. */

  /* The statement type is cached by the PreparedStatement; it need not be
   * recomputed every time. */
  assert (ps->statement_type != NULL_STATEMENT_TYPE);

  if (     ps->statement_type == isc_info_sql_stmt_exec_procedure
        && ps->out_sqlda->sqld > 0
     )
  {
    /* The crucial difference between isc_dsql_execute and isc_dsql_execute2 is
     * that the latter loads information about the first output row into the
     * output structures immediately, without waiting for a call to
     * isc_dsql_fetch().  It is IMPORTANT to prevent isc_dsql_fetch from being
     * called on a cursor that has been executed with isc_dsql_execute2. */
    {
      Transaction *trans = self->trans;
      const unsigned short dialect = Transaction_get_con(trans)->dialect;
      /* Note that we call Transaction_get_db_handle_p while holding the
       * GIL! */
      isc_tr_handle *trans_handle_p = Transaction_get_handle_p(trans);

      ENTER_GDAL
      isc_dsql_execute2(sv, trans_handle_p, &ps->stmt_handle, dialect,
          ps->in_sqlda, ps->out_sqlda
        );
      LEAVE_GDAL
      Transaction_reconsider_state(trans);

      if (DB_API_ERROR(sv)) {
        raise_sql_exception_exc_type_filter(ProgrammingError,
            "isc_dsql_execute2: ",
            sv, pyob_Cursor_execute_exception_type_filter
          );
        goto fail;
      }
      Transaction_stats_record_ps_executed(trans);
    }

    /* First, cache the result of the procedure call so that it is available
     * if and when fetch is called in the future. */
    self->exec_proc_results = XSQLDA2Tuple(self, ps->out_sqlda);
    if (self->exec_proc_results == NULL) { goto fail; }

    goto succeed;
  } else {
    Transaction *trans = self->trans;
    const unsigned short dialect = Transaction_get_con(trans)->dialect;
    /* Note that we call Transaction_get_db_handle_p while holding the
     * GIL! */
    isc_tr_handle *trans_handle_p = Transaction_get_handle_p(trans);
    assert (trans_handle_p != NULL);
    assert (*trans_handle_p != NULL_TRANS_HANDLE);

    ENTER_GDAL
    isc_dsql_execute(sv,
        trans_handle_p, &ps->stmt_handle, dialect,
        ps->in_sqlda
      );
    LEAVE_GDAL
    Transaction_reconsider_state(trans);

    if (DB_API_ERROR(sv)) {
      raise_sql_exception_exc_type_filter(ProgrammingError,
          "isc_dsql_execute: ", sv, pyob_Cursor_execute_exception_type_filter
        );
      goto fail;
    }
    Transaction_stats_record_ps_executed(trans);
  }

  assert (!DB_API_ERROR(sv));

  /* Conceptually, there should be an error in the status vector at this point
   * in cases where
   *   a SELECT statement whose target is
   *     a stored procedure
   *       that raises a database-level user-defined EXCEPTION
   * has just been executed.  However, said exception is present ONLY
   * conceptually.
   *
   * Apparently, the database API only "realizes" that a user-defined EXCEPTION
   * has been raised when it tries to fetch the first row of output from the
   * stored procedure in question.  Because of the way SELECTable stored
   * procedures work (generate... suspend; generate... suspend;), I can
   * understand why the database API works that way. */

  if (free_XSQLVAR_dynamically_allocated_memory(self) != 0) {
    goto fail_but_skip_cursor_dynmem_cleanup;
  }

  succeed:
    RETURN_PY_NONE;

  fail:
    assert (PyErr_Occurred());
    free_XSQLVAR_dynamically_allocated_memory(self);
    /* Fall through to fail_but_skip_cursor_dynmem_cleanup: */
  fail_but_skip_cursor_dynmem_cleanup:
    assert (PyErr_Occurred());

    /* Note that the cursor recovery takes place AFTER the call to
     * free_XSQLVAR_dynamically_allocated_memory, if there was such a call. */
    Cursor_recover_from_error(self);

    return NULL;
} /* Cursor_execute */

static PyObject *_Cursor_fetchtuple(Cursor *self) {
  PyObject *row = NULL;
  PreparedStatement *ps = self->ps_current;
  int statement_type;

  /* If the result set has been exhausted and the cursor hasn't executed a
   * fresh statement since, return None rather than raising an error.  This
   * behavior is required by the Python DB API. */
  if (self->last_fetch_status == RESULT_SET_EXHAUSTED) {
    RETURN_PY_NONE;
  }

  /* Raise exception if a fetch is attempted before a statement has been
   * executed: */
  if (ps == NULL) {
    raise_exception(ProgrammingError, "Cannot fetch from this cursor because"
        " it has not executed a statement."
      );
    goto fail;
  }
  statement_type = ps->statement_type;
  assert (statement_type != NULL_STATEMENT_TYPE);

  /* If the cursor's statement type is isc_info_sql_stmt_exec_procedure, then
   * pseudo-fetch a single previously cached row of results (for reasons
   * explained in the comments in function Cursor_execute regarding the
   * difference between isc_dsql_execute and isc_dsql_execute2). */
  if (statement_type == isc_info_sql_stmt_exec_procedure) {
    if (self->exec_proc_results != NULL) {
      row = self->exec_proc_results;
      /* Don't need to change reference count of exec_proc_results because
       * we're passing reference ownership to the caller of this function. */
      self->exec_proc_results = NULL;
      return row;
    } else {
      RETURN_PY_NONE;
    }
  } else if (
      /* Technically, isc_info_sql_stmt_exec_procedure can also return a result
       * set, but that statement type is handled in the clause above. */
      !(   statement_type == isc_info_sql_stmt_select
        || statement_type == isc_info_sql_stmt_select_for_upd
       )
    )
  {
    /* If the last executed statement couldn't possibly return a result set,
     * the client programmer should not be asking for one.
     * It is imperative that this situation be detected before the
     * isc_dsql_fetch call below.  Otherwise, isc_dsql_fetch will claim to have
     * succeeded without really having done so, and will leave the cursor
     * statement handle in an invalid state that causes the program to freeze
     * when isc_dsql_free_st4tement is called (usually by object destructors or
     * other cleanup code). */
    assert (ps->sql != NULL);
    assert (PyString_CheckExact(ps->sql));
    {
      PyObject *err_msg = PyString_FromFormat("Attempt to fetch row of results"
          " after statement that does not produce result set.  That statement"
          " was:  %s",
          PyString_AS_STRING(ps->sql)
        );
      if (err_msg != NULL) {
        raise_exception(ProgrammingError, PyString_AS_STRING(err_msg));
        Py_DECREF(err_msg);
      }
      goto fail;
    }
  }

  {
    const unsigned short dialect = Transaction_get_con(self->trans)->dialect;

    ENTER_GDAL
    self->last_fetch_status = isc_dsql_fetch(self->status_vector,
        &ps->stmt_handle, dialect, ps->out_sqlda
      );
    LEAVE_GDAL
  }

  /* isc_dsql_fetch return value meanings:
   *   0                    -> success
   *   RESULT_SET_EXHAUSTED -> result set exhausted
   * anything else -> error */
  switch (self->last_fetch_status) {
    case 0:
      row = XSQLDA2Tuple(self, ps->out_sqlda);
      if (row == NULL) { goto fail; }
      return row;

    case RESULT_SET_EXHAUSTED:
      {
        PreparedStatement *ps = self->ps_current;
        assert (ps != NULL);
        if (PreparedStatement_is_open(ps)) {
          if (PreparedStatement_isc_close(ps, TRUE) != 0) { goto fail; }
        }
      }

      RETURN_PY_NONE;
    /* default: Fall through to error. */
  }

  raise_sql_exception_exc_type_filter(ProgrammingError,
      "fetch: ", self->status_vector,
      pyob_Cursor_execute_exception_type_filter
    );
  /* Fall through to fail. */

  fail:
    assert (PyErr_Occurred());

    Py_XDECREF(row);

    Cursor_recover_from_error(self);

    return NULL;
} /* _Cursor_fetchtuple */

static PyObject *_Cursor_fetchmap(Cursor *self) {
  /* This function is just a wrapper around _Cursor_fetchtuple that replaces
   * that function's return tuple with a map before returning it to the client
   * programmer. */
  PyObject *row = _Cursor_fetchtuple(self);
  if (row == NULL) {
    goto fail;
  } else if (row == Py_None) {
    return row;
  } else {
    PyObject *py_result;
    PyObject *description_tuple;

    assert (py_RowMapping_constructor != NULL);
    assert (self->ps_current != NULL);

    description_tuple = PreparedStatement_description_tuple_get(
        self->ps_current
      );
    if (description_tuple == NULL) { goto fail; }

    py_result = PyObject_CallFunctionObjArgs(py_RowMapping_constructor,
        description_tuple, row, NULL
      );
    Py_DECREF(row); /* Release tuple form of the row. */
    /* Will be NULL if py_RowMapping_constructor returned error: */
    return py_result;
  }

  fail:
    assert (PyErr_Occurred());
    Py_XDECREF(row);
    return NULL;
} /* _Cursor_fetchmap */
/***************** Cursor METHODS INACCESSIBLE TO PYTHON:END *****************/

/***************** Cursor METHODS ACCESSIBLE TO PYTHON:BEGIN *****************/
static void pyob_Cursor___del__(Cursor *self) {
  assert (NOT_RUNNING_IN_CONNECTION_TIMEOUT_THREAD);

  /* Only attempt to release the cursor's fields if they've (at least begun to
   * be) initialized rather than merely nullified: */

  if (self->trans != NULL) {
    /* TAG:TRANSACTION_SUBORDINATE_OBJECT_SURVIVAL_BYPASS: */
    /* We need to make sure that the Transaction and the connection that
     * underlies it stay alive at least until we've completely the process of
     * removing their references to self.  Simply creating an artifical
     * reference to trans is not enough, since if (Transaction_is_main(trans)),
     * then trans will not actually hold a reference to its connection. */
    Transaction *trans = self->trans;

    /* Yet another subtlety is that if this destructor is being called as a
     * result of the execution of trans's destructor, we most definitely must
     * not manipulate trans's reference count, which would cause trans to be
     * "resurrected" and then for its destructor to execute recursively! */
    const boolean should_manipulate_trans_refcnt = (trans->ob_refcnt != 0);

    CConnection *con = Transaction_get_con(trans);
    PyObject *con_python_wrapper = self->con_python_wrapper;
    assert (con != NULL);
    assert (con_python_wrapper != NULL);

    Py_INCREF(con_python_wrapper);

    if (should_manipulate_trans_refcnt) {
      assert (trans->ob_refcnt != 0);
      Py_INCREF(trans);
    }


    {
    #ifdef ENABLE_CONNECTION_TIMEOUT
    const boolean needed_to_acquire_tp = !CURRENT_THREAD_OWNS_CON_TP(con);
    if (needed_to_acquire_tp) {
      ACQUIRE_CON_TP_WITH_GIL_HELD(con);
    }

    /* If the CTT caused this cursor to be untracked while this thread was
     * waiting for the lock, this thread should not close the cursor again. */
    if (!CUR_HAS_BEEN_UNTRACKED(self)) {
    #endif /* ENABLE_CONNECTION_TIMEOUT */
      const CursorState state = self->state;
      if (state != CURSOR_STATE_DROPPED && state != CURSOR_STATE_CREATED) {
        /* Close self, in the process removing self from the Transaction's open
         * cursor tracker: */
        assert (self->trans != NULL);
        assert (self->trans->open_cursors != NULL);
        Cursor_close_with_unlink(self, FALSE);
      } else {
        /* self won't be in the Transaction's open cursor tracker because self
         * has already been moved to the "dropped" state. */
        assert (self->trans == NULL);
        Cursor_close_without_unlink(self, FALSE);
      }
      assert (self->ps_current == NULL);
      assert (PSCache_has_been_deleted(&self->ps_cache_internal));
      assert (self->ps_tracker == NULL);
    #ifdef ENABLE_CONNECTION_TIMEOUT
    }
    if (needed_to_acquire_tp) {
      RELEASE_CON_TP(con);
    }
    #endif /* ENABLE_CONNECTION_TIMEOUT */
    }

    assert (self->trans == NULL);
    assert (self->con_python_wrapper == NULL);

    /* TAG:TRANSACTION_SUBORDINATE_OBJECT_SURVIVAL_BYPASS: */
    /* Release artifical local refs: */
    if (should_manipulate_trans_refcnt) {
      assert (trans->ob_refcnt != 0);
      Py_DECREF(trans);
    }
    /* Must DECREF con_python_wrapper *after* trans! */
    Py_DECREF(con_python_wrapper);
  }

  Cursor_delete(self);

  /* Release the Cursor struct itself: */
  self->ob_type->tp_free((PyObject *) self);
} /* pyob_Cursor___del__ */

static PyObject *pyob_Cursor_close(Cursor *self) {
  PyObject *res = NULL;
  /* TAG:TRANSACTION_SUBORDINATE_OBJECT_SURVIVAL_BYPASS: */
  Transaction *trans = self->trans;
  CConnection *con = NULL;
  PyObject *con_python_wrapper = NULL;

  CUR_REQUIRE_OPEN(self);

  assert (trans != NULL);

  /* TAG:TRANSACTION_SUBORDINATE_OBJECT_SURVIVAL_BYPASS: */
  /* We need to make sure that the Transaction and the connection that
   * underlies it stay alive at least until we've completely the process of
   * removing their references to self.  Simply creating an artifical reference
   * to trans is not enough, since if (Transaction_is_main(trans)), then trans
   * will not actually hold a reference to its connection. */
  con = Transaction_get_con(trans);
  assert (con != NULL);
  con_python_wrapper = self->con_python_wrapper;
  assert (con_python_wrapper != NULL);
  Py_INCREF(con_python_wrapper);
  Py_INCREF(trans);

  #ifdef ENABLE_CONNECTION_TIMEOUT
    ACQUIRE_CON_TP_WITH_GIL_HELD(con);
  #endif /* ENABLE_CONNECTION_TIMEOUT */

  if (Cursor_close_with_unlink(self, TRUE) != 0) { goto fail; }
  assert (self->trans == NULL);
  assert (self->con_python_wrapper == NULL);

  res = Py_None;
  Py_INCREF(Py_None);

  goto clean;
  fail:
    assert (PyErr_Occurred());
    /* Fall through to clean: */
  clean:
    #ifdef ENABLE_CONNECTION_TIMEOUT
      RELEASE_CON_TP(con);
    #endif /* ENABLE_CONNECTION_TIMEOUT */

    /* TAG:TRANSACTION_SUBORDINATE_OBJECT_SURVIVAL_BYPASS: */
    /* Release artifical local refs: */
    Py_DECREF(trans);
    /* Must DECREF con_python_wrapper *after* trans! */
    Py_DECREF(con_python_wrapper);

    return res;
} /* pyob_Cursor_close */

static PyObject *pyob_Cursor_prep(Cursor *self, PyObject *args) {
  PreparedStatement *ps = NULL;
  PyObject *sql;

  #ifdef ENABLE_CONNECTION_TIMEOUT
    CUR_ACTIVATE(self, return NULL);
  #endif /* ENABLE_CONNECTION_TIMEOUT */
  CUR_ENSURE_TRANSACTION_WITH_FAILURE(self, goto fail);

  if (!PyArg_ParseTuple(args, "O", &sql)) { goto fail; }

  ps = Cursor_prepare_statement(self, sql, FALSE);
  /* Regardless of the outcome, self->ps_current should've been cleared. */
  assert (self->ps_current == NULL);
  if (ps == NULL) { goto fail; }

  goto clean;
  fail:
    assert (PyErr_Occurred());
    assert (ps == NULL);
    {
      PyObject *py_ex_type, *py_ex_value, *py_ex_traceback;
      PyErr_Fetch(&py_ex_type, &py_ex_value, &py_ex_traceback);

      Cursor_recover_from_error(self);

      PyErr_Restore(py_ex_type, py_ex_value, py_ex_traceback);
    }
    assert (PyErr_Occurred());
    /* Fall through to clean: */
  clean:
    #ifdef ENABLE_CONNECTION_TIMEOUT
      CUR_PASSIVATE(self);
      CON_MUST_NOT_BE_ACTIVE(Transaction_get_con(self->trans));
    #endif /* ENABLE_CONNECTION_TIMEOUT */
    return (PyObject *) ps;
} /* pyob_Cursor_prep */

static PyObject *pyob_Cursor_execute(Cursor *self, PyObject *args) {
  PyObject *ret;
  PyObject *sql;
  PyObject *params = NULL;

  if (!PyArg_ParseTuple(args, "O|O", &sql, &params)) { return NULL; }

  #ifdef ENABLE_CONNECTION_TIMEOUT
    CUR_ACTIVATE(self, return NULL);
  #else
    CUR_REQUIRE_OPEN(self);
  #endif /* ENABLE_CONNECTION_TIMEOUT */

  if (params == NULL) {
    params = cursor_support__empty_tuple; /* No ref count change necessary. */
  }

  /* Cursor_execute or its subordinates will validate the types of sql and
   * params. */
  ret = Cursor_execute(self, sql, params);

  #ifdef ENABLE_CONNECTION_TIMEOUT
    CUR_PASSIVATE(self);
    CON_MUST_NOT_BE_ACTIVE(Transaction_get_con(self->trans));
  #endif /* ENABLE_CONNECTION_TIMEOUT */
  return ret;
} /* pyob_Cursor_execute */

static PyObject *pyob_Cursor_executemany(Cursor *self, PyObject *args) {
  /* When migrating this method from Python to C, I deliberately left out the
   * accumulation of a total rowcount for all executions.  Determining rowcount
   * is a fairly expensive operation that requires an isc_dsql_sql_info call,
   * but the executemany method is typically used in performance-sensitive
   * situations where a rowcount determination per statement execution is more
   * harmful than producing an "incorrect" result.  rowcount is only an
   * approximation anyway (see the Usage Guide entry); there are numerous
   * situations where it's not "correct". */
  PyObject *py_result = NULL;

  PyObject *sql;
  PyObject *sets_of_params;
  PyObject *sets_of_params_iterator = NULL;
  PyObject *params = NULL;

  if (!PyArg_ParseTuple(args, "OO", &sql, &sets_of_params)) { return NULL; }

  #ifdef ENABLE_CONNECTION_TIMEOUT
    CUR_ACTIVATE(self, return NULL);
  #else
    CUR_REQUIRE_OPEN(self);
  #endif /* ENABLE_CONNECTION_TIMEOUT */

  sets_of_params_iterator = PyObject_GetIter(sets_of_params);
  if (sets_of_params_iterator == NULL) { goto fail; }

  while ( (params = PyIter_Next(sets_of_params_iterator)) != NULL ) {
    PyObject *py_execute_result = Cursor_execute(self, sql, params);
    Py_DECREF(params);
    if (py_execute_result == NULL) { goto fail; }

    assert (py_execute_result == Py_None);
    Py_DECREF(py_execute_result);
  }
  /* PyIter_Next returns NULL in case of exhaustion; we need to call
   * PyErr_Occurred to see whether the termination of the loop signals
   * exhaustion or an error. */
  if (PyErr_Occurred()) { goto fail; }

  Py_INCREF(Py_None);
  py_result = Py_None;

  goto clean;
  fail:
    assert (PyErr_Occurred());
    assert (py_result == NULL);

    Cursor_recover_from_error(self);

    /* Fall through to clean: */
  clean:
    Py_XDECREF(sets_of_params_iterator);

    #ifdef ENABLE_CONNECTION_TIMEOUT
      CUR_PASSIVATE(self);
      CON_MUST_NOT_BE_ACTIVE(Transaction_get_con(self->trans));
    #endif /* ENABLE_CONNECTION_TIMEOUT */
    return py_result;
} /* pyob_Cursor_executemany */

static PyObject *pyob_Cursor_callproc(Cursor *self, PyObject *args) {
  PyObject *py_result = NULL;
  char *proc_name;
  PyObject *params = NULL;

  PyObject *sql = NULL;
  int n_params = -1;
  char *params_qmarks = NULL;

  const int QMARKS_CACHE_THRESHOLD = 16;
  #define USE_CACHED_QMARKS_STRING (n_params <= QMARKS_CACHE_THRESHOLD)
  static const char *QMARKS_CACHE[] = {
      ""                               , /*  0 */
      "?"                              , /*  1 */
      "?,?"                            , /*  2 */
      "?,?,?"                          , /*  3 */
      "?,?,?,?"                        , /*  4 */
      "?,?,?,?,?"                      , /*  5 */
      "?,?,?,?,?,?"                    , /*  6 */
      "?,?,?,?,?,?,?"                  , /*  7 */
      "?,?,?,?,?,?,?,?"                , /*  8 */
      "?,?,?,?,?,?,?,?,?"              , /*  9 */
      "?,?,?,?,?,?,?,?,?,?"            , /* 10 */
      "?,?,?,?,?,?,?,?,?,?,?"          , /* 11 */
      "?,?,?,?,?,?,?,?,?,?,?,?"        , /* 12 */
      "?,?,?,?,?,?,?,?,?,?,?,?,?"      , /* 13 */
      "?,?,?,?,?,?,?,?,?,?,?,?,?,?"    , /* 14 */
      "?,?,?,?,?,?,?,?,?,?,?,?,?,?,?"  , /* 15 */
      "?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?"  /* 16 */
    };

  if (!PyArg_ParseTuple(args, "s|O", &proc_name, &params)) { return NULL; }

  #ifdef ENABLE_CONNECTION_TIMEOUT
    CUR_ACTIVATE(self, return NULL);
  #else
    CUR_REQUIRE_OPEN(self);
  #endif /* ENABLE_CONNECTION_TIMEOUT */

  if (params != NULL) {
    const Py_ssize_t n_params_ss = PyObject_Length(params);
    if (n_params_ss == -1) {
      goto fail;
    } else if (n_params_ss > INT_MAX) {
      raise_exception(NotSupportedError, "At most INT_MAX parameters"
          " supported."
        );
      goto fail;
    }
    n_params = (int) n_params_ss; /* Safety of cast already validated above. */
  } else {
    params = cursor_support__empty_tuple; /* No ref count change necessary. */
    n_params = 0;
  }

  if (n_params == 0) {
    sql = PyString_FromFormat("EXECUTE PROCEDURE %s", proc_name);
  } else { /* n_params > 0 */
    if (USE_CACHED_QMARKS_STRING) {
      params_qmarks = (char *) QMARKS_CACHE[n_params];
    } else {
      int i;
      /* 2 chars: question mark and comma: */
      const short bytes_per_param = sizeof(char) * 2;
      const int upper_limit = n_params * bytes_per_param;
      const int qmark_string_size =
            upper_limit
              /* <-- null terminator will overwrite trailing comma, so no need
               * to allocate another byte for it */
          + 1 /* <-- extra byte; see note in loop below */
        ;

      params_qmarks = kimem_main_malloc(qmark_string_size);
      if (params_qmarks == NULL) { goto fail; }

      for (i = 0; i < upper_limit; i += bytes_per_param) {
        params_qmarks[i  ] = '?';
        /* Note that an extra byte was added to qmark_string_size so that this
         * won't write beyond the end of params_qmarks during the last
         * iteration (it could've been conditionalized, but that's a waste of
         * cycles): */
        params_qmarks[i+1] = ',';
      }
      /* Overwrite trailing comma with null character: */
      params_qmarks[upper_limit - 1] = '\0';
    }
    sql = PyString_FromFormat("EXECUTE PROCEDURE %s %s",
        proc_name, params_qmarks
      );
  }
  if (sql == NULL) { goto fail; }

  {
    PyObject *py_execute_result = Cursor_execute(self, sql, params);
    if (py_execute_result == NULL) { goto fail; }
    assert (py_execute_result == Py_None);
    Py_DECREF(py_execute_result);
  }

  /* The database engine doesn't support input/output parameters or in-place
   * memory-modified output parameters (it's like Python in this regard), so
   * return the input parameter sequence unmodified.
   * With this database engine, it's kind of silly to return anything from this
   * method, but the DB API Spec requires it.
   * To retrieve the *results* of the procedure call, use the fetch* methods,
   * as specified by the DB API Spec. */
  Py_INCREF(params);
  py_result = params;

  goto clean;
  fail:
    assert (PyErr_Occurred());
    assert (py_result == NULL);
    Cursor_recover_from_error(self);
    /* Fall through to cleanup: */
  clean:
    if (!USE_CACHED_QMARKS_STRING && params_qmarks != NULL) {
      kimem_main_free(params_qmarks);
    }
    Py_XDECREF(sql);

    #ifdef ENABLE_CONNECTION_TIMEOUT
      CUR_PASSIVATE(self);
      CON_MUST_NOT_BE_ACTIVE(Transaction_get_con(self->trans));
    #endif /* ENABLE_CONNECTION_TIMEOUT */
    return py_result;
} /* pyob_Cursor_callproc */

#define _make__fetchone_method(row_type) \
  static PyObject *pyob_Cursor_fetchone ## row_type (Cursor *self) { \
    PyObject *ret = NULL; \
    \
    CUR_ACTIVATE__FORBID_TRANSPARENT_RESUMPTION(self, return NULL); \
    CUR_ENSURE_TRANSACTION_WITH_FAILURE(self, goto fail); \
    \
    ret = _Cursor_fetch ## row_type(self); \
    if (ret == NULL) { goto fail; } \
    \
    goto clean; \
    fail: \
      assert (PyErr_Occurred()); \
      assert (ret == NULL); \
      /* Fall through to clean: */ \
    clean: \
      CUR_PASSIVATE(self); \
      CON_MUST_NOT_BE_ACTIVE(Transaction_get_con(self->trans)); \
      return ret; \
  }
_make__fetchone_method(tuple)
_make__fetchone_method(map)

static PyObject *_pyob_Cursor_fetchmany_X(Cursor *self,
    PyObject* args, PyObject* kwargs, PyObject *(*fetch_function)(Cursor *)
  )
{
  PyObject *py_result = NULL;
  Py_ssize_t i;
  Py_ssize_t size = self->arraysize;

  static char* kwarg_list[] = {"size", NULL};
  if (!PyArg_ParseTupleAndKeywords(args, kwargs,
        "|" Py_ssize_t_EXTRACTION_CODE, kwarg_list, &size
     ))
  { return NULL; }

  if (size < 0) {
    raise_exception(ProgrammingError,
        "The size parameter of the fetchmany method (which specifies the"
        " number of rows to fetch) must be greater than zero.  It is an"
        " optional parameter, so it can be left unspecifed, in which case it"
        " will default to the value of the cursor's arraysize attribute."
      );
    goto fail;
  }

  py_result = PyList_New(0);
  if (py_result == NULL) { goto fail; }

  for (i = 0; i < size; i++) {
    PyObject *row = fetch_function(self);
    if (row == NULL) {
      goto fail;
    } else if (row == Py_None) { /* Exhaustion */
      Py_DECREF(row);
      break;
    } else {
      const int append_result = PyList_Append(py_result, row);
      Py_DECREF(row);
      if (append_result == -1) { goto fail; }
    }
  }

  return py_result;

  fail:
    assert (PyErr_Occurred());

    Py_XDECREF(py_result);

    return NULL;
} /* _pyob_Cursor_fetchmany_X */

static PyObject *_pyob_Cursor_fetchmany_X__ACTIVATION_WRAPPER(
    Cursor *self,
    PyObject* args, PyObject* kwargs, PyObject *(*fetch_function)(Cursor *)
  )
{
  PyObject *ret = NULL;

  CUR_ACTIVATE__FORBID_TRANSPARENT_RESUMPTION(self, return NULL);
  CUR_ENSURE_TRANSACTION_WITH_FAILURE(self, goto fail);

  ret = _pyob_Cursor_fetchmany_X(self, args, kwargs, fetch_function);
  if (ret == NULL) { goto fail; }

  goto clean;
  fail:
    assert (PyErr_Occurred());
    assert (ret == NULL);
    /* Fall through to clean: */
  clean:
    CUR_PASSIVATE(self);
    CON_MUST_NOT_BE_ACTIVE(Transaction_get_con(self->trans));
    return ret;
} /* _pyob_Cursor_fetchmany_X__ACTIVATION_WRAPPER */

#define _make__fetchmany_method(row_type) \
  static PyObject *pyob_Cursor_fetchmany ## row_type ( \
      Cursor *self, PyObject* args, PyObject* kwargs \
    ) \
  { \
    return _pyob_Cursor_fetchmany_X__ACTIVATION_WRAPPER(self, args, kwargs, \
        _Cursor_fetch ## row_type \
      ); \
  }
_make__fetchmany_method(tuple)
_make__fetchmany_method(map)

static PyObject *_pyob_Cursor_fetchall_X(Cursor *self,
    PyObject *(*fetch_function)(Cursor *)
  )
{
  PyObject *py_result = PyList_New(0);
  if (py_result == NULL) { goto fail; }

  for (;;) {
    PyObject *row = fetch_function(self);
    if (row == NULL) {
      goto fail;
    } else if (row == Py_None) {
      Py_DECREF(row);
      break;
    } else {
      const int append_result = PyList_Append(py_result, row);
      Py_DECREF(row);
      if (append_result == -1) { goto fail; }
    }
  }

  return py_result;

  fail:
    assert (PyErr_Occurred());

    Py_XDECREF(py_result);

    return NULL;
} /* _pyob_Cursor_fetchall_X */

static PyObject *_pyob_Cursor_fetchall_X__ACTIVATION_WRAPPER(
    Cursor *self, PyObject *(*fetch_function)(Cursor *)
  )
{
  PyObject *ret = NULL;

  CUR_ACTIVATE__FORBID_TRANSPARENT_RESUMPTION(self, return NULL);
  CUR_ENSURE_TRANSACTION_WITH_FAILURE(self, goto fail);

  ret = _pyob_Cursor_fetchall_X(self, fetch_function);
  if (ret == NULL) { goto fail; }

  goto clean;
  fail:
    assert (PyErr_Occurred());
    assert (ret == NULL);
    /* Fall through to clean: */
  clean:
    CUR_PASSIVATE(self);
    CON_MUST_NOT_BE_ACTIVE(Transaction_get_con(self->trans));
    return ret;
} /* _pyob_Cursor_fetchall_X__ACTIVATION_WRAPPER */

#define _make__fetchall_method(row_type) \
  static PyObject *pyob_Cursor_fetchall ## row_type (Cursor *self) { \
    return _pyob_Cursor_fetchall_X__ACTIVATION_WRAPPER(self, \
        _Cursor_fetch ## row_type \
      ); \
  }
_make__fetchall_method(tuple)
_make__fetchall_method(map)

#define _make__iterator_method(row_type) \
  static PyObject *pyob_Cursor_iter ## row_type (PyObject *self) { \
    PyObject *it = NULL; \
    PyObject *bound_fetch_method; \
    \
    CUR_ACTIVATE__FORBID_TRANSPARENT_RESUMPTION((Cursor *) self, return NULL); \
    \
    bound_fetch_method = PyObject_GetAttr( \
        self, cursor_support__method_name__fetchone ## row_type \
      ); \
    if (bound_fetch_method == NULL) { goto fail; } \
    \
    it = PyCallIter_New(bound_fetch_method, Py_None); \
    /* Iterator it contains its own ref to bound_fetch_method: */ \
    Py_DECREF(bound_fetch_method);  \
    if (it == NULL) { goto fail; } \
    \
    goto clean; \
    fail: \
      assert (PyErr_Occurred()); \
      assert (it == NULL); \
      /* Fall through to clean: */ \
    clean: \
      CUR_PASSIVATE((Cursor *) self); \
      CON_MUST_NOT_BE_ACTIVE(Transaction_get_con(((Cursor *) self)->trans)); \
      return it; \
  }
_make__iterator_method(tuple)
_make__iterator_method(map)

static PyObject *pyob_Cursor_DBAPICompatDoNothing_VARARGS(
    Cursor *self, PyObject *args
  )
{
  /* Various Cursor methods (typically those that allow the client programmer
   * to manually deliver "optimization hints") are defined for the sake of
   * compatibility with the Python DB API, but do nothing because the database
   * engine's C API doesn't support the features that would be required for the
   * methods to actually improve performance, or because kinterbasdb's type
   * translation machinery is too dynamic for the methods to actually improve
   * performance, or because kinterbasdb's automatic optimization already does
   * an excellent job. */
  RETURN_PY_NONE;
} /* pyob_Cursor_DBAPICompatDoNothing */

/****************** Cursor METHODS ACCESSIBLE TO PYTHON:END ******************/

/****************** Cursor ATTRIBUTE GET/SET METHODS:BEGIN *******************/

static PyObject *pyob_Cursor_arraysize_get(Cursor *self, void *closure) {
  CUR_REQUIRE_OPEN(self);

  return PyInt_FromSsize_t(self->arraysize);
} /* pyob_Cursor_arraysize_get */

static int pyob_Cursor_arraysize_set(Cursor *self, PyObject *value,
    void *closure
  )
{
  CUR_REQUIRE_OPEN_WITH_FAILURE(self, return -1);

  if (value == NULL) { goto fail; } /* Deletion attempt. */
  {
    #ifdef PYTHON_2_5_OR_LATER
    const Py_ssize_t value_ss = PyInt_AsSsize_t(value);
    #else
    const long value_ss = PyInt_AsLong(value);
    #endif
    if (PyErr_Occurred() || value_ss < 0 || value_ss > PY_SSIZE_T_MAX) {
      goto fail;
    }
    self->arraysize =
      #ifndef PYTHON_2_5_OR_LATER
        (Py_ssize_t)
      #endif
      value_ss;
  }

  return 0;

  fail: {
    PyObject *err_msg = PyString_FromFormat("The arraysize attribute can"
        " only be set to an int between 0 and " Py_ssize_t_STRING_FORMAT
        " (inclusive), and cannot be deleted.",
        PY_SSIZE_T_MAX
      );
    if (err_msg != NULL) {
      raise_exception(ProgrammingError, PyString_AS_STRING(err_msg));
      Py_DECREF(err_msg);
    }
    return -1;
  }
} /* pyob_Cursor_arraysize_set */

static PyObject *pyob_Cursor_connection_get(Cursor *self, void *closure) {
  /* Returns an instance of Python class kinterbasdb.Connection, *not* an
   * instance of CConnection. */
  CUR_REQUIRE_OPEN(self);
  assert (self->trans != NULL);
  assert (self->con_python_wrapper != NULL);

  Py_INCREF(self->con_python_wrapper);
  return self->con_python_wrapper;
} /* pyob_Cursor_connection_get */

static PyObject *pyob_Cursor_transaction_get(Cursor *self, void *closure) {
  CUR_REQUIRE_OPEN_WITH_FAILURE(self, goto fail);

  assert (!CUR_HAS_BEEN_UNTRACKED(self));
  {
    PyObject *trans = (PyObject *) self->trans;
    Py_INCREF(trans);
    return trans;
  }

  fail:
    assert (PyErr_Occurred());
    /* The only reason for this property retrieval to fail is that the Cursor
     * is not open.  If that's the case, then the Cursor's Transaction
     * pointer should be NULL. */
    assert (CUR_HAS_BEEN_UNTRACKED(self));
    return NULL;
} /* pyob_Cursor_transaction_get */

static PyObject *pyob_Cursor_description_get(Cursor *self, void *closure) {
  /* DB API description tuple read-only property. */
  PyObject *py_result = NULL;

  CUR_REQUIRE_OPEN(self);

  /* If we haven't executed a statement yet, self->ps_current will be NULL.  In
   * that case, the Python DB API requires the return value to be None. */
  if (self->ps_current == NULL) {
    py_result = Py_None;
  } else {
    py_result = PreparedStatement_description_tuple_get(self->ps_current);
    if (py_result == NULL) { return NULL; }
  }

  Py_INCREF(py_result);
  return py_result;
} /* pyob_Cursor_description_get */

static PyObject *pyob_Cursor_name_get(Cursor *self, void *closure) {
  PyObject *py_result;

  CUR_ACTIVATE__FORBID_TRANSPARENT_RESUMPTION(self, return NULL);

  py_result = (self->name != NULL ? self->name : Py_None);
  Py_INCREF(py_result);

  CUR_PASSIVATE(self);
  CON_MUST_NOT_BE_ACTIVE(Transaction_get_con(self->trans));
  return py_result;
} /* pyob_Cursor_name_get */

static int pyob_Cursor_name_set(Cursor *self, PyObject *value,
    void *closure
  )
{
  int ret = 0;
  PreparedStatement *ps = self->ps_current;

  CUR_ACTIVATE__FORBID_TRANSPARENT_RESUMPTION(self, return -1);

  if (value == NULL || !PyString_CheckExact(value)) {
    PyErr_SetString(PyExc_TypeError, "The name attribute can only be set to a"
        " string, and cannot be deleted."
      );
    goto fail;
  }

  if (ps == NULL || ps->stmt_handle == NULL_STMT_HANDLE) {
    raise_exception_with_numeric_error_code(ProgrammingError, -901,
        "This cursor has not yet executed a statement, so setting its name"
        " attribute would be meaningless."
      );
    goto fail;
  }

  if (self->name != NULL) {
    /* Cannot reset the cursor's name while operating in the context of the
     * same statement for which the original name was declared.  (The code
     * that clears the cursor for the execution of another statement also
     * clears its name field, so we need only compare it to NULL here.) */
    raise_exception_with_numeric_error_code(ProgrammingError, -502,
        "Cannot set this cursor's name, because its name has already been"
        " declared in the context of the statement that the cursor is"
        " currently executing."
      );
    goto fail;
  }

  /* Now make the cursor name association inside the database. */
  {
    /* Call PyString_AS_STRING only while holding GIL: */
    char *c_name = PyString_AS_STRING(value);
    ENTER_GDAL
    isc_dsql_set_cursor_name(self->status_vector, &ps->stmt_handle, c_name,
        0 /* Last argument is "reserved for future use". */
      );
    LEAVE_GDAL
  }
  if (DB_API_ERROR(self->status_vector)) {
    raise_sql_exception(OperationalError, "Could not set cursor name: ",
        self->status_vector
      );
    goto fail;
  }

  /* Store the name str for retrieval by pyob_Cursor_name_get. */
  Py_INCREF(value);
  self->name = value;

  assert (ret == 0);
  goto clean;
  fail:
    assert (PyErr_Occurred());
    ret = -1;
    /* Fall through to clean: */
  clean:
    CUR_PASSIVATE(self);
    CON_MUST_NOT_BE_ACTIVE(Transaction_get_con(self->trans));
    return ret;
} /* pyob_Cursor_name_set */

static PyObject *pyob_Cursor_rowcount_get(Cursor *self, PyObject *args) {
  PyObject *ret = NULL;
  int cursor_stmt_type;

  const char request_params[] = {isc_info_sql_records, isc_info_end};

  /* The fixed size of res_buf introduces the possibility of a buffer overflow,
   * but it's extremely unlikely because we know quite a bit about the size
   * requirements (they're not affected by input from the client programmer, or
   * anything like that).  */
  char res_buf[512];

  char *res_walk;
  long cur_count = -1;
  char cur_count_type; /* What type of statement does this count concern? */

  PreparedStatement *ps = self->ps_current;

  CUR_ACTIVATE__FORBID_TRANSPARENT_RESUMPTION(self, return NULL);

  if (   ps == NULL
      || (cursor_stmt_type = ps->statement_type) == NULL_STATEMENT_TYPE
     )
  { goto cannot_determine; }

  assert(ps->stmt_handle != NULL_STMT_HANDLE);

  if (   cursor_stmt_type != isc_info_sql_stmt_select
      && cursor_stmt_type != isc_info_sql_stmt_insert
      && cursor_stmt_type != isc_info_sql_stmt_update
      && cursor_stmt_type != isc_info_sql_stmt_delete
    )
  { goto cannot_determine; }

  ENTER_GDAL

  isc_dsql_sql_info(self->status_vector,
      &ps->stmt_handle,
      sizeof(request_params), (char *) request_params,
      sizeof(res_buf), res_buf
    );
  if (DB_API_ERROR(self->status_vector)) {
    LEAVE_GDAL_WITHOUT_ENDING_CODE_BLOCK
    raise_sql_exception(OperationalError, "pyob_Cursor_rowcount_get: ",
        self->status_vector
      );
    goto fail;
  }

  /* res_buf[0] indicates what type of information is being returned (in this
   * situation, it never varies). */
  assert (res_buf[0] == isc_info_sql_records);

  /* Start res_walk after the first 3 bytes, which are infrastructural. */
  res_walk = res_buf + 3;
  while ( (cur_count_type = *res_walk) != isc_info_end ) {
    res_walk += 1;
    {
      const short length_of_cur_count_in_buffer = (short) isc_vax_integer(
          res_walk, sizeof(short)
        );
      res_walk += sizeof(short);
      cur_count = isc_vax_integer(res_walk, length_of_cur_count_in_buffer);
      res_walk += length_of_cur_count_in_buffer;
    }

    /* If the count that we've just extracted from the result buffer concerns
     * the same statement type as the last statement executed by the cursor,
     * immediately return that count to the Python level.
     * All temporary storage in this function is allocated on the stack, so
     * there's nothing for us to manually release. */
    if (
         (   cur_count_type == isc_info_req_select_count
          && cursor_stmt_type == isc_info_sql_stmt_select
         ) || (
             cur_count_type == isc_info_req_insert_count
          && cursor_stmt_type == isc_info_sql_stmt_insert
         ) || (
             cur_count_type == isc_info_req_update_count
          && cursor_stmt_type == isc_info_sql_stmt_update
         ) || (
             cur_count_type == isc_info_req_delete_count
          && cursor_stmt_type == isc_info_sql_stmt_delete
         )
       )
    {
      LEAVE_GDAL_WITHOUT_ENDING_CODE_BLOCK
      ret = PyInt_FromLong(cur_count);
      goto clean;
    }
  } /* end of "while not at end of result buffer..." loop */

  LEAVE_GDAL

  /* Fall through to cannot_determine. */
  cannot_determine:
    /* The Python DB API requires us to return -1 in cases where the row count
     * cannot be determined. */
    assert (!PyErr_Occurred());
    ret = PyInt_FromLong(-1);
    goto clean;
  fail:
    assert (PyErr_Occurred());
    assert (ret == NULL);
    /* Fall through to clean: */
  clean:
    CUR_PASSIVATE(self);
    CON_MUST_NOT_BE_ACTIVE(Transaction_get_con(self->trans));
    return ret;
} /* pyob_Cursor_rowcount_get */

/******************* Cursor ATTRIBUTE GET/SET METHODS:END ********************/

/************************** UTILITY FUNCTIONS:BEGIN **************************/

static PyObject *pyob_CursorOrConnection_is_purportedly_open(
    PyObject *self, PyObject *args
  )
{
  /* Only "purportedly" open because the underlying network connection could
   * fail at any time; the state flag alone obviously can't account for such
   * problems. */
  PyObject *incoming;

  if (!PyArg_ParseTuple(args, "O", &incoming)) { return NULL; }

  if (PyObject_TypeCheck(incoming, &ConnectionType)) {
    return PyBool_FromLong(
        ((CConnection *) incoming)->state == CON_STATE_OPEN
      );
  } else if (PyObject_TypeCheck(incoming, &CursorType)) {
    return PyBool_FromLong(
        ((Cursor *) incoming)->state == CURSOR_STATE_OPEN
      );
  } else {
    PyErr_SetString(PyExc_TypeError,
        "Object must be of type ConnectionType or CursorType."
      );
    return NULL;
  }
} /* pyob_CursorOrConnection_is_purportedly_open */

/*************************** UTILITY FUNCTIONS:END ***************************/

/************* Cursor CLASS DEFINITION AND INITIALIZATION:BEGIN **************/

static PyMethodDef Cursor_methods[] = {
    {"close", (PyCFunction) pyob_Cursor_close, METH_NOARGS},

    {"prep", (PyCFunction) pyob_Cursor_prep, METH_VARARGS},

    {"execute", (PyCFunction) pyob_Cursor_execute, METH_VARARGS},
    {"executemany", (PyCFunction) pyob_Cursor_executemany, METH_VARARGS},
    {"callproc", (PyCFunction) pyob_Cursor_callproc, METH_VARARGS},

    /* fetch methods: */
    /* At the C level, the 'fetch*' series had to become 'fetch*tuple' because
     * MSVC 6 and 7 don't support variadic macros. */
    {"fetchone", (PyCFunction) pyob_Cursor_fetchonetuple, METH_NOARGS},
    {"fetchonemap", (PyCFunction) pyob_Cursor_fetchonemap, METH_NOARGS,
        "Identical to fetchone, except returns a map of field name to field"
        " value, rather than a sequence."
      },

    {"fetchmany", (PyCFunction) pyob_Cursor_fetchmanytuple,
        METH_VARARGS|METH_KEYWORDS
      },
    {"fetchmanymap", (PyCFunction) pyob_Cursor_fetchmanymap,
        METH_VARARGS|METH_KEYWORDS,
        "Identical to fetchmany, except returns a sequence of maps of field"
        " name to field value, rather than a sequence of sequences."
      },

    {"fetchall", (PyCFunction) pyob_Cursor_fetchalltuple, METH_NOARGS},
    {"fetchallmap", (PyCFunction) pyob_Cursor_fetchallmap, METH_NOARGS,
        "Identical to fetchall, except returns a sequence of maps of field"
        " name to field value, rather than a sequence of sequences."
      },

    /* The special method "__iter__" is defined by setting the appropriate slot
     * (tp_iter) in the CursorType structure.  The "iter" method here is the
     * same function; it's included only for symmetry with "itermap". */
    {"iter", (PyCFunction) pyob_Cursor_itertuple, METH_NOARGS,
        "Returns iterator over remaining rows (sequences)."
      },
    {"itermap", (PyCFunction) pyob_Cursor_itermap, METH_NOARGS,
        "Returns iterator over remaining rows (maps)."
      },

    /* Dynamic type trans getters/setters for Cursor (defined in
     * _kiconversion_type_translation.c): */
    { "set_type_trans_out", (PyCFunction) pyob_Cursor_set_type_trans_out,
        METH_VARARGS, "See \"Dynamic Type Translation\" topic in Usage Guide."
      },
    { "get_type_trans_out", (PyCFunction) pyob_Cursor_get_type_trans_out,
        METH_NOARGS, "See \"Dynamic Type Translation\" topic in Usage Guide."
      },
    { "set_type_trans_in",  (PyCFunction) pyob_Cursor_set_type_trans_in,
        METH_VARARGS, "See \"Dynamic Type Translation\" topic in Usage Guide."
      },
    { "get_type_trans_in",  (PyCFunction) pyob_Cursor_get_type_trans_in,
        METH_NOARGS, "See \"Dynamic Type Translation\" topic in Usage Guide."
      },

    /* The following methods are defined for compatibility with the Python DB
     * API, but they do nothing: */
    {"setinputsizes",
        (PyCFunction) pyob_Cursor_DBAPICompatDoNothing_VARARGS, METH_VARARGS,
        "DB API Optional Method.  Present, but does nothing."
      },
    {"setoutputsize",
        (PyCFunction) pyob_Cursor_DBAPICompatDoNothing_VARARGS, METH_VARARGS,
        "DB API Optional Method.  Present, but does nothing."
      },

    {NULL}  /* sentinel */
  };

static PyGetSetDef Cursor_getters_setters[] = {
    {"arraysize",
        (getter) pyob_Cursor_arraysize_get,
        (setter) pyob_Cursor_arraysize_set,
        "The arraysize attribute is defined only in order to conform to the"
        " Python DB API Spec."
        "\nThe Firebird C API does not support bulk fetch, so the value of the"
        " arraysize attribute does not affect perfomance at all."
        "  The Python DB API specification still requires that arraysize be"
        " present, and that the fetchmany() method take its value into account"
        " if fetchmany()'s optional $size parameter is not specified."
      },

    {"connection",
        (getter) pyob_Cursor_connection_get,
        NULL,
        "DB API Extension:  the kinterbasdb.Connection associated with this"
        " cursor."
      },

    {"transaction",
        (getter) pyob_Cursor_transaction_get,
        NULL,
        "DB API Extension:  the kinterbasdb.Transaction associated with this"
        " cursor."
      },

    {"description",
        (getter) pyob_Cursor_description_get,
        NULL,
        "DB API description tuple (either None or a sequence of 7-item"
        " sequences)."
    },

    {"name", /* Cursor name support (for SQL statements that use
              * "WHERE CURRENT OF %name"): */
        (getter) pyob_Cursor_name_get,
        (setter) pyob_Cursor_name_set,
        "For use with \"[UPDATE|DELETE] ... WHERE CURRENT OF ...\" statements"
        " (see the \"Named Cursors\" topic in Usage Guide)."
      },

    {"rowcount",
        (getter) pyob_Cursor_rowcount_get,
        NULL,
        "DB API rowcount.  Not always accurate; see \"rowcount attribute\""
        " topic in Usage Guide."
      },

    {NULL}  /* sentinel */
  };

PyTypeObject CursorType = { /* new-style class */
    PyObject_HEAD_INIT(NULL)
    0,                                  /* ob_size */
    "kinterbasdb.Cursor",               /* tp_name */
    sizeof(Cursor),                     /* tp_basicsize */
    0,                                  /* tp_itemsize */
    (destructor) pyob_Cursor___del__,   /* tp_dealloc */
    0,                                  /* tp_print */
    0,                                  /* tp_getattr */
    0,                                  /* tp_setattr */
    0,                                  /* tp_compare */
    0,                                  /* tp_repr */
    0,                                  /* tp_as_number */
    0,                                  /* tp_as_sequence */
    0,                                  /* tp_as_mapping */
    0,                                  /* tp_hash */
    0,                                  /* tp_call */
    0,                                  /* tp_str */
    0,                                  /* tp_getattro */
    0,                                  /* tp_setattro */
    0,                                  /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_ITER,
                                        /* tp_flags */
    0,                                  /* tp_doc */
    0,		                              /* tp_traverse */
    0,		                              /* tp_clear */
    0,		                              /* tp_richcompare */
    0,		                              /* tp_weaklistoffset */

    pyob_Cursor_itertuple,		          /* tp_iter */
    0,		                              /* tp_iternext */

    Cursor_methods,                     /* tp_methods */
    NULL,                               /* tp_members */
    Cursor_getters_setters,             /* tp_getset */
    0,                                  /* tp_base */
    0,                                  /* tp_dict */
    0,                                  /* tp_descr_get */
    0,                                  /* tp_descr_set */
    0,                                  /* tp_dictoffset */

    (initproc) Cursor_init,             /* tp_init */
    0,                                  /* tp_alloc */
    pyob_Cursor_new,                    /* tp_new */
    0,                                  /* tp_free */
    0,                                  /* tp_is_gc */
    0,                                  /* tp_bases */
    0,                                  /* tp_mro */
    0,                                  /* tp_cache */
    0,                                  /* tp_subclasses */
    0                                   /* tp_weaklist */
  };

static int init_kidb_cursor(void) {
  cursor_support__empty_tuple = PyTuple_New(0);
  if (cursor_support__empty_tuple == NULL) { goto fail; }

  /* Presence of 'tuple' in C variable name and absence in string constant is
   * deliberate (needed because MSVC 6 and 7 lack variadic macro support): */
  ISWEC(cursor_support__method_name__fetchonetuple, "fetchone");

  ISWEC(cursor_support__method_name__fetchonemap, "fetchonemap");

  /* CursorType is a new-style class, so PyType_Ready must be called before
   * its getters and setters will function. */
  if (PyType_Ready(&CursorType) < 0) { goto fail; }

  return 0;

  fail:
    /* This function is indirectly called by the module loader, which makes no
     * provision for error recovery. */
    return -1;
} /* init_kidb_cursor */

/************* Cursor CLASS DEFINITION AND INITIALIZATION:END **************/

/***** CursorTracker MEMBER FUNC DEFINITIONS AND SUPPORTING FUNCS: BEGIN *****/
#include "_kisupport_lifo_linked_list.h"

LIFO_LINKED_LIST_DEFINE_BASIC_METHODS_PYALLOC_NOQUAL(CursorTracker, Cursor)
/****** CursorTracker MEMBER FUNC DEFINITIONS AND SUPPORTING FUNCS: END ******/
