/* 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>
 */

/****************** DECLARATIONS:BEGIN *******************/
static int PreparedStatement_close_without_unlink(
    PreparedStatement *, boolean
  );

static int PSTracker_remove(PSTracker **, PreparedStatement *cont, boolean);

static int PreparedStatement_isc_close(PreparedStatement *, boolean);

static PyObject *_generic_single_item_isc_dsql_sql_info_request(
    isc_stmt_handle *, ISC_STATUS *, const char, const short
  );
static int _determine_statement_type(isc_stmt_handle *, ISC_STATUS *);

typedef int (*PSCacheMappedFunction)(
    PSCache *, unsigned short, PreparedStatement *
  );

#define PSCache_has_been_deleted(self) \
  ((self)->container == NULL)

/* Note that PS_STATE_CLOSED has a very different meaning from the "closed"
 * states of most other objects: */
#define PreparedStatement_is_open(self) \
  ((self)->state == PS_STATE_OPEN || (self)->state == PS_STATE_CLOSED)

/* Python strings are immutable, and Python's garbage collector does not
 * relocate memory, so if two PyObject pointers point to the same memory
 * location, the two objects are certainly equal--in fact, they're IDentical
 * (id(self->sql) == id(sql)).
 *
 * If the pointers refer to different memory locations, the two objects are
 * still equal if their contents match. */
#define PreparedStatement_matches_sql(self_, sql_) \
  (   (self_)->sql == (sql_) \
   || PyObject_Compare((self_)->sql, (sql_)) == 0 \
  )
/****************** DECLARATIONS:END *******************/

/*************** PSCache (PreparedStatementList) METHODS:BEGIN ***************/

#define PSCACHE_NULLIFY(self) \
  (self)->container = NULL; \
  (self)->capacity = 0; \
  (self)->start = 0; \
  (self)->most_recently_found = NULL;

static int PSCache_initialize(PSCache *self, unsigned short capacity) {
  unsigned short i;

  self->container = kimem_main_malloc(sizeof(PreparedStatement *) * capacity);
  if (self->container == NULL) { return -1; }

  self->capacity = capacity;
  for (i = 0; i < capacity; i++) {
    self->container[i] = NULL;
  }

  self->most_recently_found = NULL;

  return 0;
} /* PSCache_initialize */

#if (PREP_STMT_CACHE_CAPACITY == 1)
  #define _PSCache_index_next(i) 0
  #define _PSCache_index_prev(i) 0
#else
  #define _PSCache_index_next(i) \
    ((i + 1) % self->capacity)

  #define _PSCache_index_prev(i) \
    (i != 0 ? i - 1 : self->capacity - 1)
#endif

#define PSCache_first(self) \
  _PSCache_index_prev(self->start)

#define PSCache_is_empty(self) \
  (PSCache_first(self) == NULL)

static int PSCache_append(PSCache *self, PreparedStatement *ps) {
  const unsigned short i = self->start;

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

  /* Any PreparedStatement added to a Cursor's PSCache should be for internal
   * user, so we can enforce specific expectations about its reference
   * count: */
  assert (ps->for_internal_use);
  assert (ps->ob_refcnt == 1);

  assert (ps->sql != NULL);
  assert (OBJECT_IS_VALID_TYPE_FOR_PREPARED_STATEMENT_SQL(ps->sql));
  assert (ps->cur != NULL);

  if (Cursor_ensure_PSCache(ps->cur) != 0) { return -1; }

  {
    PreparedStatement *prev_occupant = self->container[i];
    if (prev_occupant != NULL) {
      /* We should never be appending to the cache a PreparedStatement that's
       * already present.  The following assertion isn't a robust check, of
       * course. */
      assert (prev_occupant != ps);

      /* We mustn't delete a PreparedStatement, yet have it still referenced by
       * the self->most_recently_found shortcut pointer. */
      if (prev_occupant == self->most_recently_found) {
        self->most_recently_found = NULL;
      }

      /* Since all PreparedStatements stored in a PSCache are for internal use,
       * and internal use is tightly controllable, we know that removing
       * prev_occupant from the cache will cause it to be deleted. */
      assert (prev_occupant->ob_refcnt == 1);

      Py_DECREF(prev_occupant);
    }
  }
  Py_INCREF(ps);
  self->container[i] = ps;

  self->start = _PSCache_index_next(i);

  return 0;
} /* PSCache_append */

static PreparedStatement *PSCache_find_prep_stmt_for_sql(PSCache *self,
    PyObject *sql
  )
{
  /* Returns a borrowed reference to a PreparedStatement that matches sql, or
   * NULL if there is no such PreparedStatement in the cache. */
  assert (!PSCache_has_been_deleted(self));

  assert (sql != NULL);
  assert (OBJECT_IS_VALID_TYPE_FOR_PREPARED_STATEMENT_SQL(sql));

  if (self->most_recently_found != NULL) {
    assert (self->most_recently_found->sql != NULL);
    if (PreparedStatement_matches_sql(self->most_recently_found, sql)) {
      return self->most_recently_found;
    }
  }{

  PreparedStatement *matching_ps = NULL;
  /* self->start is the index where the next appended statement should be
   * placed, but we want to start with the most recently appended statement and
   * work backward from there. */
  unsigned short i = _PSCache_index_prev(self->start);
  const unsigned short i_orig = i;

  do {
    PreparedStatement *ps = self->container[i];
    if (ps == NULL) { break; } /* None left to search. */
    assert (OBJECT_IS_VALID_TYPE_FOR_PREPARED_STATEMENT_SQL(ps->sql));

    if (PreparedStatement_matches_sql(ps, sql)) {
      matching_ps = self->most_recently_found = ps;
      break;
    }
    i = _PSCache_index_prev(i);
  } while (i != i_orig);

  return matching_ps;
}} /* PSCache_find_prep_stmt_for_sql */

static int PSCache_traverse(PSCache *self, PSCacheMappedFunction modifier) {
  unsigned short i = _PSCache_index_prev(self->start);
  const unsigned short i_orig = i;

  assert (!PSCache_has_been_deleted(self));

  do {
    PreparedStatement *ps = self->container[i];
    if (ps == NULL) { break; }{ /* None left to search. */

    const int modifier_result = modifier(self, i, ps);
    if (modifier_result != 0) { return modifier_result; }
    i = _PSCache_index_prev(i);
  }} while (i != i_orig);

  return 0;
} /* PSCache_traverse */

static void PSCache_clear(PSCache *self) {
  /* Empties the PSCache, but does not release any of the dynamically allocated
   * memory of its members (we're talking about PSCache *structure members*,
   * not *container elements*). */
  unsigned short i;

  assert (!PSCache_has_been_deleted(self));

  i = self->start;
  for (;;) {
    i = _PSCache_index_prev(i);
    {
      PreparedStatement *ps = self->container[i];
      if (ps == NULL) {
        break;
      }
      /* Only internal PreparedStatements should be in the PSCache: */
      assert (ps->for_internal_use);

      /* At this point, each internal PreparedStatement should be referenced
       * once by the tracker: */
      assert (ps->ob_refcnt != 0);
      /* But nowhere else: */
      assert (ps->ob_refcnt == 1);

      Py_DECREF(ps);
      self->container[i] = NULL;
    }
  }

  self->start = 0;
  self->most_recently_found = NULL;
} /* PSCache_clear */

static void PSCache_delete(PSCache *self) {
  /* Opposite of PSCache_initialize. */
  assert (!PSCache_has_been_deleted(self));

  assert (self->container != NULL);

  PSCache_clear(self);
  /* Must ensure that a dangling reference to a deleted member of ->container
   * wasn't left behind. */
  assert (self->most_recently_found == NULL);

  kimem_main_free(self->container);
  self->container = NULL;

  self->capacity = 0;
} /* PSCache_delete */

/**************** PSCache (PreparedStatementList) METHODS:END ****************/

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

#define PS_REQ_OPEN_WITH_FAILURE(ps, failure_action) \
  if (_PreparedStatement_require_open(ps, NULL) != 0) { failure_action; }

#define PS_REQ_OPEN(ps) \
  PS_REQ_OPEN_WITH_FAILURE(ps, return NULL)

#define PS_REQ_OPEN2(ps, failure_message) \
  if (_PreparedStatement_require_open(ps, failure_message) != 0) { \
    return NULL; \
  }

static int _PreparedStatement_require_open(
    PreparedStatement *self, char *failure_message
  )
{
  /* If self is not open, raises the supplied error message (or a default if
   * no error message is supplied).
   * Returns 0 if self was open; -1 if it was closed. */
  if (PreparedStatement_is_open(self)) { return 0; }

  if (self->state == PS_STATE_CONNECTION_TIMED_OUT) {
    raise_exception(ConnectionTimedOut,
        "This PreparedStatement's connection timed out, and"
        " PreparedStatements cannot transparently survive a timeout."
      );
  } else {
    raise_exception(ProgrammingError,
        failure_message != NULL ? failure_message :
          "The PreparedStatement must be OPEN to perform this"
          " operation."
      );
  }
  return -1;
} /* _PreparedStatement_require_open */

static void PreparedStatement_create_references_to_superiors(
    PreparedStatement *self, const boolean for_internal_use,
    Cursor *cur
  )
{
  assert (self != NULL);
  assert (self->cur == NULL);
  assert (cur != NULL);
  /* The internal-use indicator that we've been provided should match the one
   * set in the corresponding member of self: */
  assert (for_internal_use == self->for_internal_use);

  /* An non-internal (user-accessible) PreparedStatement owns a reference to
   * its Cursor, while its Cursor does not own a reference to it.  Reference
   * ownership for an internal PreparedStatement is just the opposite. */
  if (!for_internal_use) {
    Py_INCREF(cur);
  }
  self->cur = cur;
} /* PreparedStatement_create_references_to_superiors */

static void PreparedStatement_clear_references_to_superiors(
    PreparedStatement *self
  )
{
  assert (self != NULL);
  {
    Cursor *cur = self->cur;
    assert (cur != NULL);
    self->cur = NULL;
    if (!self->for_internal_use) {
      Py_DECREF(cur);
    }
  }
} /* PreparedStatement_clear_references_to_superiors */

static PreparedStatement *PreparedStatement_create(
    Cursor *cur, boolean for_internal_use
  )
{
  PreparedStatement *self;

  self = PyObject_New(PreparedStatement, &PreparedStatementType);
  if (self == NULL) { goto fail; }

  self->state = PS_STATE_CREATED;
  self->for_internal_use = for_internal_use;
  self->stmt_handle = NULL_STMT_HANDLE;
  self->sql = NULL;

  assert (cur != NULL);
  self->cur = NULL;
  PreparedStatement_create_references_to_superiors(
      self, for_internal_use, cur
    );
  assert (self->cur == cur);

  self->statement_type = NULL_STATEMENT_TYPE;

  self->in_sqlda = NULL;
  self->in_sqlda_sqlind_array = NULL;
  self->out_sqlda = NULL;

  self->in_var_orig_spec = NULL;

  self->out_buffer = NULL;

  self->description = NULL;

  /* Notice that this constructor does not add self to any caches or trackers.
   * It is the caller's responsibility to do so, if appropriate. */

  return self;

  fail:
    Py_XDECREF(self);

    return NULL;
} /* PreparedStatement_create */

static int PreparedStatement_open(PreparedStatement* self,
    Cursor *cur, PyObject *sql
  )
{
  ISC_STATUS *sv;
  Transaction *trans;
  CConnection *con;

  assert (cur != NULL);
  sv = cur->status_vector;

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

  con = Transaction_get_con(trans);
  assert (con != NULL);

  CON_MUST_ALREADY_BE_ACTIVE(con);

  /* The caller should've already validated sql; sql should now be a non-NULL
   * str. */
  assert (sql != NULL);
  assert (OBJECT_IS_VALID_TYPE_FOR_PREPARED_STATEMENT_SQL(sql));

  Py_INCREF(sql);
  self->sql = sql;

  /* Allocate new statement handle: */
  assert(self->stmt_handle == NULL_STMT_HANDLE);
  ENTER_GDAL
  isc_dsql_allocate_statement(sv, &con->db_handle, &self->stmt_handle);
  LEAVE_GDAL
  if (DB_API_ERROR(sv)) {
    raise_sql_exception(OperationalError, "isc_dsql_allocate_statement: ", sv);
    goto fail;
  }
  assert(self->stmt_handle != NULL_STMT_HANDLE);

  /* Allocate enough space for a default number of XSQLVARs in the OUTput
   * XSQLDA.  The XSQLDA must be allocated prior to calling isc_dsql_prepare
   * below, and if there are too many output variables for the default size,
   * it may need to be reallocated and re-initialized with isc_dsql_describe
   * later. */
  assert (self->out_sqlda == NULL);
  if (reallocate_sqlda(&self->out_sqlda, FALSE, NULL) < 0) { goto fail; }
  assert (self->out_sqlda != NULL);

  /* Ask the database engine to compile the statement. */
  {
    /* Note that we call Transaction_get_handle_p while holding the GIL. */
    isc_tr_handle *trans_handle_addr = Transaction_get_handle_p(trans);
    char *sql_raw_buffer = PyString_AS_STRING(sql);
    const Py_ssize_t sql_len = PyString_GET_SIZE(sql);

    if (!_check_statement_length(sql_len)) { goto fail; }

    ENTER_GDAL
    isc_dsql_prepare(sv, trans_handle_addr, &self->stmt_handle,
        (unsigned short) sql_len, sql_raw_buffer, con->dialect,
        self->out_sqlda
      );
    LEAVE_GDAL
    if (DB_API_ERROR(sv)) {
      raise_sql_exception(ProgrammingError, "isc_dsql_prepare: ", sv);
      goto fail;
    }
  }

  /* Determine the database API's internal statement type code for the current
   * statement and cache that code. */
  assert (self->statement_type == NULL_STATEMENT_TYPE);
  self->statement_type = _determine_statement_type(&self->stmt_handle, sv);
  if (self->statement_type == -1) { goto fail; }
  assert (self->statement_type != NULL_STATEMENT_TYPE);

  /* Now that we know how many output variables there are, it might be
   * necessary to resize the output XSQLDA to accomodate them. */
  {
    const int sqlda_realloc_res = reallocate_sqlda(&self->out_sqlda, FALSE, NULL);
    if (sqlda_realloc_res == 0) {
      /* No actual reallocation was necessary, so there's no need to rebind. */
    } else if (sqlda_realloc_res == 1) {
      /* The default number of XSQLVARs allocated earlier was insufficient, so
       * the XSQLDA's parameter information must be rebound. */
      ENTER_GDAL
      isc_dsql_describe(sv, &self->stmt_handle, con->dialect,
          self->out_sqlda /* OUTPUT */
        );
      LEAVE_GDAL
      if (DB_API_ERROR(sv)) {
        raise_sql_exception(OperationalError,
            "isc_dsql_describe for OUTput params: ", sv
          );
        goto fail;
      }
    } else {
      /* reallocate_sqlda raised an error. */
      goto fail;
    }
  }

  /* If there are any output variables, allocate a buffer to hold the raw
   * values from the database, before they're converted to Python values.  This
   * buffer will be allocated to exactly the required size, and will then
   * remain alive throughout the life of this PreparedStatement, accepting
   * every row of raw output every fetched with the statement. */
  assert (self->out_buffer == NULL);
  if (self->out_sqlda->sqld > 0) {
    self->out_buffer = allocate_output_buffer(self->out_sqlda);
    if (self->out_buffer == NULL) { goto fail; }
  }

  /* Bind information about the INput XSQLDA's variables. */

  /* Allocate enough space for a default number of XSQLVARs in the INput
   * XSQLDA.  The XSQLDA must be allocated prior to calling
   * isc_dsql_describe_bind below, and it may need to be reallocated prior if
   * the default number of XSQLVARs was insufficient. */
  assert (self->in_sqlda == NULL);
  if (reallocate_sqlda(&self->in_sqlda, TRUE, &self->in_sqlda_sqlind_array)
      < 0
     )
  { goto fail; }
  assert (self->in_sqlda != NULL);

  ENTER_GDAL
  isc_dsql_describe_bind(sv, &self->stmt_handle, con->dialect,
      self->in_sqlda /* INPUT */
    );
  LEAVE_GDAL
  if (DB_API_ERROR(sv)) {
    raise_sql_exception(OperationalError,
        "isc_dsql_describe_bind for INput params: ", sv
      );
    goto fail;
  }

  {
    const int sqlda_realloc_res = reallocate_sqlda(&self->in_sqlda, TRUE,
        &self->in_sqlda_sqlind_array
      );
    if (sqlda_realloc_res == 0) {
      /* No actual reallocation was necessary, so there's no need to rebind. */
    } else if (sqlda_realloc_res == 1) {
      /* The default number of XSQLVARs allocated earlier was insufficient, so
       * the XSQLDA's parameter information must be rebound. */
      ENTER_GDAL
      isc_dsql_describe_bind(sv, &self->stmt_handle, con->dialect,
          self->in_sqlda  /* INPUT */
        );
      LEAVE_GDAL
      if (DB_API_ERROR(sv)) {
        raise_sql_exception(OperationalError,
            "isc_dsql_describe_bind for INput params: ", sv
          );
        goto fail;
      }
    } else {
      /* reallocate_sqlda raised an error. */
      goto fail;
    }
  }

  /* Record the original type and size information for input parameters so that
   * information can be restored if implicit input parameter conversion from
   * string is invoked. */
  {
    XSQLVAR *sqlvar;
    OriginalXSQLVARSpecificationCache *spec_cache;
    short var_no;
    const short input_param_count = self->in_sqlda->sqld;

    self->in_var_orig_spec = kimem_plain_malloc(
        sizeof(OriginalXSQLVARSpecificationCache) * input_param_count
      );
    if (self->in_var_orig_spec == NULL) {
      /* Weren't calling one of the Python-supplied memory allocators; need to
       * set Python exception (MemoryError). */
      PyErr_NoMemory();
      goto fail;
    }

    for ( sqlvar = self->in_sqlda->sqlvar, var_no = 0,
            spec_cache = self->in_var_orig_spec;
          var_no < input_param_count;
          sqlvar++, var_no++, spec_cache++
        )
    {
      spec_cache->sqltype = sqlvar->sqltype;
      spec_cache->sqllen = sqlvar->sqllen;
    }
  }

  /* The description tuple should still be NULL; it will be computed lazily
   * only if the client programmer asks for it. */
  assert (self->description == NULL);

  assert (!PyErr_Occurred());
  self->state = PS_STATE_OPEN;
  return 0;

  fail:
    assert (PyErr_Occurred());
    /* The state flag should indicate that the PreparedStatement has been
     * created but not successfully opened. */
    assert (self->state == PS_STATE_CREATED);
    return -1;
} /* PreparedStatement_open */

static int PreparedStatement_compare(
    PreparedStatement *a, PreparedStatement *b
  )
{
  PyObject *a_sql = a->sql;
  PyObject *b_sql = b->sql;
  Cursor *a_cur = a->cur;
  Cursor *b_cur = b->cur;

  if (    (a_sql == NULL || b_sql == NULL)
       || (a_cur == NULL || b_cur == NULL)
       || (a_cur->trans != b_cur->trans)
     )
  {
    /* Not equal. */
    return -1;
  } else {
    /* Equality comes down to the equality of their SQL strings. */
    return PyObject_Compare(a_sql, b_sql);
  }
} /* PreparedStatement_compare */

static PyObject *PreparedStatement_description_tuple_get(
    PreparedStatement *self
  )
{
  /* Lazily creates a Python DB API Cursor.description tuple for this
   * PreparedStatement.
   * Returns borrowed reference to the description tuple, or NULL on error. */
  assert (self->out_sqlda != NULL);
  assert (self->cur != NULL);

  if (self->description == NULL) {
    /* The object created by the following call will be DECREFed by
     * PreparedStatement_clear_description_tuple, which may be called directly
     * by the PreparedStatement destructor or indirectly by
     * (Connection|Cursor).set_type_trans_out. */
    self->description = XSQLDA2Description(self->out_sqlda, self->cur);
  }

  return self->description;
} /* PreparedStatement_description_tuple_get */

static PyObject *pyob_PreparedStatement_description_get(
    PreparedStatement *self, void *closure
  )
{
  /* DB API description tuple read-only property. */
  PS_REQ_OPEN(self);

  {
    PyObject *py_result = PreparedStatement_description_tuple_get(self);
    /* PreparedStatement_description_tuple_get returns a *borrowed* reference
     * to the description tuple; we must increment it (with Py_XINCREF, because
     * it might be NULL) before returning it. */
    Py_XINCREF(py_result);
    return py_result;
  }
} /* pyob_Cursor_description_get */

/* CLEAR-DESCRIPTION-TUPLE SUPPORT CODE : BEGIN */
/* The description tuple contains information about a statement's output
 * variables that might change if (Connection|Cursor).set_type_trans_out is
 * called.  Therefore, set_type_trans_out was changed to trigger the clearing
 * of the description tuple of every PreparedStatement subordinate to the
 * object on which the method was invoked (a Connection or Cursor).  This group
 * of functions supports that operation.
 *
 * Note that the description tuples are cleared, not reconstructed.  A given
 * description tuple will only be reconstructed if the user actually requests
 * it. */

static int PreparedStatement_clear_description_tuple(PreparedStatement *self) {
  /* Clears an individual PreparedStatement's description tuple. */
  if (self->description != NULL) {
    Py_DECREF(self->description);
    self->description = NULL;
  }
  return 0;
} /* PreparedStatement_clear_description_tuple */

static int PSCacheMapped_clear_description_tuple(
    PSCache* cache, unsigned short cache_index, PreparedStatement *ps
  )
{
  /* Called on each element of a Cursor's PSCache's container. */
  assert (ps != NULL);
  return PreparedStatement_clear_description_tuple(ps);
} /* PSCacheMapped_clear_description_tuple */

static int PSTrackerMapped_clear_description_tuple(
    PSTracker *node_prev, PSTracker *node_cur
  )
{
  /* Called on each element of a Cursor's PSTracker. */
  PreparedStatement *ps;
  assert (node_cur != NULL);
  ps = node_cur->contained;
  assert (ps != NULL);
  return PreparedStatement_clear_description_tuple(ps);
} /* PSTrackerMapped_clear_description_tuple */

static int Cursor_clear_ps_description_tuples(Cursor *self) {
  /* Clears the description tuple of every PreparedStatement subordinate to
   * this Cursor. */
  PSCache *psc = &self->ps_cache_internal;

  if (!PSCache_has_been_deleted(psc)) {
    if (PSCache_traverse(psc, PSCacheMapped_clear_description_tuple) != 0)
    { goto fail; }
  }

  if (self->ps_tracker != NULL) {
    if (PSTracker_traverse(self->ps_tracker,
            PSTrackerMapped_clear_description_tuple
          ) != 0
       )
    { goto fail; }
  }

  return 0;

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

static int CConnection_clear_ps_description_tuples(
    CConnection *self
  )
{
  /* Clears the description tuple of every PreparedStatement subordinate to
   * this Connection. */
  TransactionTracker *trans_node = self->transactions;
  while (trans_node != NULL) {
    Transaction *trans = trans_node->contained;
    assert (trans != NULL);
    {
      CursorTracker *cur_node = trans->open_cursors;
      while (cur_node != NULL) {
        Cursor *cur = cur_node->contained;
        assert (cur != NULL);
        if (Cursor_clear_ps_description_tuples(cur) != 0) { return -1; }
        cur_node = cur_node->next;
      }
    }
    trans_node = trans_node->next;
  }
  return 0;
} /* CConnection_clear_ps_description_tuples */
/* CLEAR-DESCRIPTION-TUPLE SUPPORT CODE : END */

static int PreparedStatement_isc_close(PreparedStatement *self,
    boolean allowed_to_raise
  )
{
  /* IB6 API Guide page 334: "A cursor need only be closed in this manner [with
   * DSQL_close] if it was previously opened and associated with stmt_handle by
   * isc_dsql_set_cursor_name()."
   *
   * That's not accurate.  A statement handle also needs to be closed in this
   * manner if it's about to be executed again.  If it's going to be closed
   * *permanently*, calling isc_dsql_free_statement with DSQL_drop (as is done
   * in PreparedStatement_isc_drop) is sufficient. */
  ISC_STATUS *sv;
  assert (self->cur != NULL);
  assert (self->cur->trans != NULL);
  assert (Transaction_get_con(self->cur->trans) != NULL);

  CON_MUST_ALREADY_BE_ACTIVE(Transaction_get_con(self->cur->trans));

  sv = self->cur->status_vector;

  assert (PreparedStatement_is_open(self));

  ENTER_GDAL
  isc_dsql_free_statement(sv, &self->stmt_handle,
      /* DSQL_close means "close the open result set associated with this
       * statement; we're clearing it for another execution." */
      DSQL_close
    );
  LEAVE_GDAL
  if (DB_API_ERROR(sv)) {
    raise_sql_exception(OperationalError,
        "Error while trying to close PreparedStatement's associated result"
        " set: ", sv
      );
    if (allowed_to_raise) {
      return -1;
    } else {
      SUPPRESS_EXCEPTION;
    }
  }

  self->state = PS_STATE_CLOSED;

  /* We "closed" this prepared statement in a sense that matters only to the
   * server, not to the Python client programmer: */
  assert (PreparedStatement_is_open(self));
  assert (self->stmt_handle != NULL_STMT_HANDLE);

  return 0;
} /* PreparedStatement_isc_close */

static int PreparedStatement_isc_drop(PreparedStatement *self,
    boolean allowed_to_raise
  )
{
  ISC_STATUS *sv;
  assert (self->cur != NULL);
  assert (self->cur->trans != NULL);
  #if (defined(ENABLE_CONNECTION_TIMEOUT) && !defined(NDEBUG))
  {
    CConnection *con = Transaction_get_con(self->cur->trans);
    assert (con != NULL);
    if (Connection_timeout_enabled(con)) {
      assert (CURRENT_THREAD_OWNS_CON_TP(con));
      if (RUNNING_IN_CONNECTION_TIMEOUT_THREAD) {
        assert (con->timeout->state == CONOP_IDLE);
      }
    }
  }
  #endif

  sv = self->cur->status_vector;

  /* Notice that unlike in PreparedStatement_isc_close, there's no
   *   assert (PreparedStatement_is_open(self));
   * statement here.  That's because it's possible for a statement handle to
   * be allocated, but for preparation to fail later in the process, as when
   * there's a syntax error.  In that case, self's state won't have been moved
   * to PS_STATE_OPEN. */

  {
    /* This code can be reached when the CTT is timing out a connection.  In
     * that case, we want the GIL to remain held during the entire timeout
     * operation. */
    OPEN_LOCAL_GIL_MANIPULATION_INFRASTRUCTURE
    #ifdef ENABLE_CONNECTION_TIMEOUT
    const boolean should_manip_gil = NOT_RUNNING_IN_CONNECTION_TIMEOUT_THREAD;
    if (should_manip_gil) {
    #endif
      LEAVE_GIL_WITHOUT_AFFECTING_DB_AND_WITHOUT_STARTING_CODE_BLOCK
    #ifdef ENABLE_CONNECTION_TIMEOUT
    }
    #endif
    ENTER_GDAL_WITHOUT_LEAVING_PYTHON

    isc_dsql_free_statement(sv, &self->stmt_handle,
        /* DSQL_drop means "free resources allocated for this statement; we're
         * closing it permanently." */
        DSQL_drop
      );

    LEAVE_GDAL_WITHOUT_ENTERING_PYTHON
    #ifdef ENABLE_CONNECTION_TIMEOUT
    if (should_manip_gil) {
    #endif
      ENTER_GIL_WITHOUT_AFFECTING_DB_AND_WITHOUT_ENDING_CODE_BLOCK
    #ifdef ENABLE_CONNECTION_TIMEOUT
    }
    #endif
    CLOSE_LOCAL_GIL_MANIPULATION_INFRASTRUCTURE
  } /* end of lock manipulation scope */

  if (DB_API_ERROR(sv)) {
    raise_sql_exception(OperationalError,
        "Error while trying to drop PreparedStatement's statement handle: ",
        sv
      );
    if (allowed_to_raise) {
      return -1;
    } else {
      SUPPRESS_EXCEPTION;
    }
  }

  self->stmt_handle = NULL_STMT_HANDLE;
  self->state = PS_STATE_DROPPED;

  assert (!PreparedStatement_is_open(self));

  return 0;
} /* PreparedStatement_isc_drop */

static int PreparedStatement_close_without_unlink(PreparedStatement *self,
    boolean allowed_to_raise
  )
{
  if (self->sql != NULL) {
    Py_DECREF(self->sql);
    self->sql = NULL;
  }

  if (self->in_sqlda != NULL) {
    kimem_xsqlda_free(self->in_sqlda);
    self->in_sqlda = NULL;
  }

  if (self->in_sqlda_sqlind_array != NULL) {
    kimem_main_free(self->in_sqlda_sqlind_array);
    self->in_sqlda_sqlind_array = NULL;
  }

  if (self->out_sqlda != NULL) {
    kimem_xsqlda_free(self->out_sqlda);
    self->out_sqlda = NULL;
  }

  if (self->in_var_orig_spec != NULL) {
    kimem_plain_free(self->in_var_orig_spec);
    self->in_var_orig_spec = NULL;
  }

  if (self->out_buffer != NULL) {
    kimem_main_free(self->out_buffer);
    self->out_buffer = NULL;
  }

  PreparedStatement_clear_description_tuple(self);

  /* Save the operations that might fail for last: */
  if (self->cur != NULL) {
    /* If self is the cursor's current PreparedStatement, need to ensure that
     * the cursor realizes self has closed so it won't try to perform
     * operations using self in the future. */
    if (self->cur->ps_current == self) {
      if (self->cur->state != CURSOR_STATE_CLOSED) {
        Cursor_clear_and_leave_open(self->cur);
      }
      /* The lack of Py_DECREF here is deliberate, because the ps_current
       * member of Cursor never (conceptually) contains an owned reference. */
      self->cur->ps_current = NULL;
    }
    /* Note that we don't DECREF or clear self->cur here; that's for
     * PreparedStatement_close_with_unlink. */
  }

  if (self->stmt_handle != NULL_STMT_HANDLE) {
    assert (self->cur != NULL);
    if (PreparedStatement_isc_drop(self, allowed_to_raise) != 0) {
      goto fail;
    }
  }
  assert (self->stmt_handle == NULL_STMT_HANDLE);

  /* We don't clear self->cur here or remove self from cur's tracker.  That's
   * up to PreparedStatement_close_with_unlink. */

  self->state = PS_STATE_DROPPED;
  return 0;

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

static int PreparedStatement_untrack_with_superior_ref_clear_control(
    PreparedStatement *self, const boolean allowed_to_raise,
    const boolean clear_superior_refs
  )
{
  if (PreparedStatement_close_without_unlink(self, allowed_to_raise) != 0) {
    return -1;
  }
  assert (self->state == PS_STATE_DROPPED);
  assert (self->cur != NULL);
  if (clear_superior_refs) {
    PreparedStatement_clear_references_to_superiors(self);
    assert (self->cur == NULL);
  }

  return 0;
} /* PreparedStatement_untrack_with_superior_ref_clear_control */

static int PreparedStatement_untrack(PreparedStatement *self,
    boolean allowed_to_raise
  )
{
  return PreparedStatement_untrack_with_superior_ref_clear_control(
      self, allowed_to_raise, TRUE
    );
} /* PreparedStatement_untrack */

static int PreparedStatement_close_with_unlink(PreparedStatement *self,
    boolean allowed_to_raise
  )
{
  if (self->state != PS_STATE_DROPPED) {
    if (PreparedStatement_close_without_unlink(self, allowed_to_raise) != 0) {
      goto fail;
    }
  }

  if (self->cur != NULL) {
    /* If self was for internal use, there's no need to manually remove self
     * from cur->cur->ps_cache_internal, because the cur will only kill one of
     * its internal use PreparedStatements as part of the act of removing it
     * from cur->ps_cache_internal. */
    if (!self->for_internal_use) {
      /* Remove self from the cursor's open prepared statement tracker.
       * Normally self will be in the ps_tracker, but PreparedStatements are
       * not inserted until the end of the preparation process, so if an error
       * occurred, self won't be there.  This can occur routinely (e.g., if
       * user submits erroneous SQL to Cursor.prep), so with the final boolean
       * parameter, we direct PSTracker_remove not to complain if the self is
       * missing. */
      if (PSTracker_remove(&self->cur->ps_tracker, self, FALSE) != 0) {
        if (allowed_to_raise) {
          goto fail;
        } else {
          SUPPRESS_EXCEPTION;
        }
      }
    }

    PreparedStatement_clear_references_to_superiors(self);
    assert (self->cur == NULL);
  }

  assert (allowed_to_raise ? self->state == PS_STATE_DROPPED : TRUE);
  return 0;

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

/*********** PreparedStatement METHODS INACCESSIBLE TO PYTHON:END ************/

/*********** PreparedStatement METHODS ACCESSIBLE TO PYTHON:BEGIN ************/
static void pyob_PreparedStatement___del__(PreparedStatement *self) {
  assert (
        !self->for_internal_use
      ? NOT_RUNNING_IN_CONNECTION_TIMEOUT_THREAD
      : TRUE
    );

  if (self->cur != NULL) {
    Cursor *cur = self->cur;

    /* We want to make sure that cur remains alive until we're done destroying
     * self, but if this destructor is being called as a result of the
     * execution of cur's destructor, we most definitely must not manipulate
     * cur's reference count, which would cause cur to be "resurrected" and
     * then for its destructor to execute again!
     * Also, if self is for internal use, it never physically owns a reference
     * to the cursor, so the reference count should not be manipulated. */
    const boolean should_manipulate_cursor_refcnt = (
        !self->for_internal_use && cur->ob_refcnt != 0
      );

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

    { /* hoop-jump for C90's retarded scoping */
    const boolean needed_to_acquire_tp = !CURRENT_THREAD_OWNS_CON_TP(con);

    /* Make sure con stays alive until we're done with it.  Note that cur
     * might not stay alive until the end of this destructor. */
    if (should_manipulate_cursor_refcnt) {
      assert (cur->ob_refcnt != 0);
      Py_INCREF(cur);
    }
    Py_INCREF(con);
    Py_INCREF(con_python_wrapper);

    #ifdef ENABLE_CONNECTION_TIMEOUT
    if (needed_to_acquire_tp) {
      ACQUIRE_CON_TP_WITH_GIL_HELD(con);
    }
    #endif

    if (PreparedStatement_close_with_unlink(self, TRUE) == 0) {
      assert (self->cur == NULL);
    } else {
      SUPPRESS_EXCEPTION;
    }

    #ifdef ENABLE_CONNECTION_TIMEOUT
    if (needed_to_acquire_tp) {
      RELEASE_CON_TP(con);
    }
    #endif /* ENABLE_CONNECTION_TIMEOUT */
    } /* hoop-jump for C90's retarded scoping */

    if (should_manipulate_cursor_refcnt) {
      assert (cur->ob_refcnt != 0);
      Py_DECREF(cur);
    }
    Py_DECREF(con);
    Py_DECREF(con_python_wrapper);
  } /* end of if (self->cur != NULL) block */

  /* Release the PreparedStatement struct itself: */
  PyObject_Del(self);
} /* pyob_PreparedStatement___del__ */
/************ PreparedStatement METHODS ACCESSIBLE TO PYTHON:END *************/

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

static PyObject *pyob_PreparedStatement_sql_get(
    PreparedStatement *self, void *closure
  )
{
  PyObject *py_result;

  PS_REQ_OPEN(self);

  py_result = self->sql != NULL ? self->sql : Py_None;
  Py_INCREF(py_result);
  return py_result;
} /* pyob_PreparedStatement_sql_get */

static PyObject *pyob_PreparedStatement_statement_type_get(
    PreparedStatement *self, void *closure
  )
{
  /* A PreparedStatement cannot function even minimally without knowing its own
   * statement type, so kinterbasdb should never have allowed a PS that doesn't
   * know its statement type to become accessible to Python client code.
   *
   * Because a PreparedStatement's underlying Curosr could close without the
   * PS's consent, though, we ensure that the PS is open before returning the
   * statement_type. */
  int statement_type;

  PS_REQ_OPEN(self);

  statement_type = self->statement_type;
  if (statement_type != NULL_STATEMENT_TYPE) {
    return PyInt_FromLong(statement_type);
  } else {
    raise_exception(InternalError, "This PreparedStatement does not know its"
        " own statement_type; kinterbasdb should not have allowed it to become"
        " accessible to client code."
      );
    return NULL;
  }
} /* pyob_PreparedStatement_statement_type_get */

static PyObject *pyob_PreparedStatement_plan_get(
    PreparedStatement *self, void *closure
  )
{
  PyObject *ret = NULL;

  PS_REQ_OPEN(self);

  assert (self->cur != NULL);
  #ifdef ENABLE_CONNECTION_TIMEOUT
    CUR_ACTIVATE__FORBID_TRANSPARENT_RESUMPTION(self->cur, return NULL);
  #endif /* ENABLE_CONNECTION_TIMEOUT */

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

  ret = _generic_single_item_isc_dsql_sql_info_request(
      &self->stmt_handle, self->cur->status_vector, isc_info_sql_get_plan,
      1 /* Skip 1 byte (newline character at beginning of plan string. */
    );
  if (ret == NULL) { goto fail; }

  goto clean;
  fail:
    assert (PyErr_Occurred());
    if (ret != NULL) {
      Py_DECREF(ret);
      ret = NULL;
    }
    /* Fall through to clean: */
  clean:
    #ifdef ENABLE_CONNECTION_TIMEOUT
      CUR_PASSIVATE(self->cur);
      CON_MUST_NOT_BE_ACTIVE(Transaction_get_con(self->cur->trans));
    #endif /* ENABLE_CONNECTION_TIMEOUT */
    return ret;
} /* pyob_PreparedStatement_plan_get */

#define _make_n_direction_params_getter(direction) \
  /* direction is expected to be 'in' or 'out'. */ \
  static PyObject *pyob_PreparedStatement_n_ ## direction ## put_params_get( \
      PreparedStatement *self, void *closure \
    ) \
  { \
    PS_REQ_OPEN(self); \
    assert (self->cur != NULL); \
    { \
      XSQLDA *sqlda = self->direction ## _sqlda; \
      \
      if (sqlda != NULL) { \
        return PyInt_FromLong(sqlda->sqld); \
      } else { \
        raise_exception(InternalError, "Unexpected PreparedStatement state:" \
            " the PS is considered 'open', but has no " # direction "put" \
            "_sqlda." \
          ); \
        return NULL; \
      } \
    } \
  }

_make_n_direction_params_getter(in)
_make_n_direction_params_getter(out)

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

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

static boolean _check_statement_length(Py_ssize_t length) {
  /* Although the sql-statement-length parameter to such Firebird API functions
   * as isc_dsql_prepare and isc_dsql_execute_immediate is an unsigned short,
   * the documentation says that the length can be left zero for null-
   * terminated strings, in which case the database engine will figure out the
   * length itself.
   *   As of 2003.02.13, Firebird cannot handle SQL statements longer than the
   * maximum value of an unsigned short even if zero is passed as the length.
   *
   * Test the length and raise an exception if it's too long for safe passage
   * to SQL-handling isc_* functions.  Return TRUE if OK; FALSE otherwise. */
  assert (length >= 0);

  if (length <= (Py_ssize_t) USHRT_MAX) {
    return TRUE;
  } else {
    PyObject *py_length = PyLong_FromUnsignedLongLong(
        (unsigned LONG_LONG) length
      );
    if (py_length != NULL) {
      PyObject *py_length_str = PyObject_Str(py_length);
      if (py_length_str != NULL) {
        PyObject *err_msg = PyString_FromFormat(
            "SQL statement of %s bytes is too long (max %d allowed). Consider"
            " using bound parameters to shorten the SQL code, rather than"
            " passing large values as part of the SQL string.",
            PyString_AS_STRING(py_length_str), USHRT_MAX
          );
        if (err_msg != NULL) {
          raise_exception(ProgrammingError, PyString_AS_STRING(err_msg));
          Py_DECREF(err_msg);
        }
        Py_DECREF(py_length_str);
      }
      Py_DECREF(py_length);
    }
    return FALSE;
  }
} /* _check_statement_length */

static PyObject *_generic_single_item_isc_dsql_sql_info_request(
    isc_stmt_handle *stmt_handle, ISC_STATUS *sv,
    const char request_code, const short skip_bytes_at_beginning_of_result
  )
{
  char req_buf[] = {0};
  char *res_buf = NULL;
  /* The size of the buffer is automatically increased if necessary. */
  unsigned short res_buf_size = 128;
  short result_length = -1;
  PyObject *py_result = NULL;

  ENTER_GDAL

  req_buf[0] = request_code;

  for (;;) {
    assert (res_buf == NULL);
    res_buf = kimem_plain_malloc(res_buf_size);
    if (res_buf == NULL) {
      LEAVE_GDAL_WITHOUT_ENDING_CODE_BLOCK
      /* Was calling kimem_plain_malloc; need to set MemoryError explicitly. */
      PyErr_NoMemory();
      goto fail;
    }

    isc_dsql_sql_info(sv, stmt_handle, sizeof(req_buf), req_buf,
        res_buf_size, res_buf
      );
    if (DB_API_ERROR(sv)) {
      LEAVE_GDAL_WITHOUT_ENDING_CODE_BLOCK
      raise_sql_exception(OperationalError, "isc_dsql_sql_info failed: ", sv);
      goto fail;
    }

    {
      const char first_byte = res_buf[0];

      if (first_byte == isc_info_truncated) {
        /* The result buffer wasn't large enough.  Free the original result
         * buffer and double the integer that will determine its size in the
         * next pass, then jump to the next pass. */
        kimem_plain_free(res_buf);
        res_buf = NULL;
        res_buf_size *= 2;
        continue;
      } else if (first_byte == isc_info_end) {
        /* No result is available for this request; return None. */
        LEAVE_GDAL_WITHOUT_ENDING_CODE_BLOCK
        Py_INCREF(Py_None);
        py_result = Py_None;
        goto exit;
      } else if (first_byte != request_code) {
        LEAVE_GDAL_WITHOUT_ENDING_CODE_BLOCK
        {
          PyObject *err_msg = PyString_FromFormat(
              "Unexpected code in result buffer.  Expected %c; got %c.",
              request_code, first_byte
            );
          if (err_msg != NULL) {
            raise_exception(InternalError, PyString_AS_STRING(err_msg));
            Py_DECREF(err_msg);
          }
          goto fail;
        }
      }
    }

    result_length = (short) isc_vax_integer(res_buf + 1, sizeof(short));
    break;
  }

  LEAVE_GDAL

  assert (result_length >= 0);

  if (skip_bytes_at_beginning_of_result > result_length) {
    raise_exception(InternalError,
        "byte skip directive would overflow result."
      );
    goto fail;
  }

  {
    const short read_n_bytes =
      result_length - skip_bytes_at_beginning_of_result;
    if (read_n_bytes != 0) {
      py_result = PyString_FromStringAndSize(
          res_buf + 3 + skip_bytes_at_beginning_of_result, read_n_bytes
        );
    } else {
      py_result = PyString_FromStringAndSize("", 0);
    }
  }

  if (py_result != NULL) {
    goto exit;
  }
  /* Fall through to fail: */

  fail:
    assert (PyErr_Occurred());
    if (py_result != NULL) {
      Py_DECREF(py_result);
      py_result = NULL;
    }
    /* Fall through to exit: */

  exit:
    if (res_buf != NULL) {
      kimem_plain_free(res_buf);
    }

    return py_result;
} /* _generic_single_item_isc_dsql_sql_info_request */

static int _determine_statement_type(
    isc_stmt_handle *stmt_handle, ISC_STATUS *status_vector
  )
{
  /* Given a pointer to the handle of a prepared statement, dynamically
   * determine the database engine's internal statement type code.
   *
   * This function is essentially a special case of
   * _generic_single_item_isc_dsql_sql_info_request, optimized for speed
   * (it must be called for each and every statement preparation in
   * kinterbasdb). */
  int stmt_type;
  short stmt_type_length;
  static const char sql_info_stmt_type_req[] = {isc_info_sql_stmt_type};
  #define sql_info_res_buf_size 8
  char sql_info_res_buf[sql_info_res_buf_size];

  ENTER_GDAL

  isc_dsql_sql_info(status_vector, stmt_handle,
      sizeof(sql_info_stmt_type_req),
      #ifndef FIREBIRD_1_5_OR_LATER
        (char *) /* Cast away constness. */
      #endif
        sql_info_stmt_type_req,
      sql_info_res_buf_size, sql_info_res_buf
    );
  if (DB_API_ERROR(status_vector)) {
    LEAVE_GDAL_WITHOUT_ENDING_CODE_BLOCK
    raise_sql_exception(OperationalError, "_determine_statement_type: ",
        status_vector
      );
    goto fail;
  }

  {
    const char first_byte = sql_info_res_buf[0];

    if (first_byte == isc_info_truncated) {
      /* Since we know the required size of the information item we're
       * requesting, this should never actually happen (and therefore we don't
       * need to adjust the size dynamically). */
      LEAVE_GDAL_WITHOUT_ENDING_CODE_BLOCK
      raise_exception(InternalError, "_determine_statement_type:  statically"
          " sized result buffer was too small."
        );
      goto fail;
    } else if (first_byte != isc_info_sql_stmt_type) {
      LEAVE_GDAL_WITHOUT_ENDING_CODE_BLOCK
      raise_exception(InternalError, "_determine_statement_type:  expected"
          " first byte of result buffer to be isc_info_sql_stmt_type."
        );
      goto fail;
    }
  }

  stmt_type_length = (short)
      isc_vax_integer(sql_info_res_buf + 1, sizeof(short) )
    ;
  stmt_type = (int) isc_vax_integer(sql_info_res_buf + 3, stmt_type_length);

  LEAVE_GDAL
  return stmt_type;

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

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

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

static PyMethodDef PreparedStatement_methods[] = {
    {NULL}  /* sentinel */
  };

static PyGetSetDef PreparedStatement_getters_setters[] = {
    {"sql",
        (getter) pyob_PreparedStatement_sql_get,
        NULL,
        "ASCII representation of the raw SQL string that underlies this"
        " PreparedStatement."
      },
    {"statement_type",
        (getter) pyob_PreparedStatement_statement_type_get,
        NULL,
        "An int that matches one of the kinterbasdb.isc_info_sql_stmt_*"
        " constants.  For example, the following comparison is True:"
        "\n  cur.prep(\"insert into test values (?,?)\").statement_type =="
        " kinterbasdb.isc_info_sql_stmt_insert"
      },
    {"plan",
        (getter) pyob_PreparedStatement_plan_get,
        NULL,
        "The PLAN string generated by the optimizer."
      },
    {"n_input_params",
        (getter) pyob_PreparedStatement_n_input_params_get,
        NULL,
        "The number of input parameters expected by the statement."
      },
    {"n_output_params",
        (getter) pyob_PreparedStatement_n_output_params_get,
        NULL,
        "The number of output parameters returned by the statement."
      },
    {"description",
        (getter) pyob_PreparedStatement_description_get,
        NULL,
        "A DB API description tuple (of the same format as Cursor.description)"
        " that describes the statement's output parameters."
      },
    {NULL}  /* sentinel */
  };

PyTypeObject PreparedStatementType = { /* new-style class */
    PyObject_HEAD_INIT(NULL)
    0,                                  /* ob_size */
    "kinterbasdb.PreparedStatement",    /* tp_name */
    sizeof(PreparedStatement),          /* tp_basicsize */
    0,                                  /* tp_itemsize */
    (destructor) pyob_PreparedStatement___del__, /* tp_dealloc */
    0,                                  /* tp_print */
    0,                                  /* tp_getattr */
    0,                                  /* tp_setattr */
    (cmpfunc) PreparedStatement_compare,/* 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 */
    0,                                  /* tp_flags */
    0,                                  /* tp_doc */
    0,		                              /* tp_traverse */
    0,		                              /* tp_clear */
    0,		                              /* tp_richcompare */
    0,		                              /* tp_weaklistoffset */

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

    PreparedStatement_methods,          /* tp_methods */
    NULL,                               /* tp_members */
    PreparedStatement_getters_setters,  /* tp_getset */
    0,                                  /* tp_base */
    0,                                  /* tp_dict */
    0,                                  /* tp_descr_get */
    0,                                  /* tp_descr_set */
    0,                                  /* tp_dictoffset */

    /* Currently using PreparedStatement_create only at the C level, so there's
     * no conventional __init__ method: */
    0,                                  /* tp_init */

    0,                                  /* tp_alloc */
    0,                                  /* tp_new */
  };

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

  return 0;

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

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

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

LIFO_LINKED_LIST_DEFINE_BASIC_METHODS_PYALLOC_NOQUAL(PSTracker, PreparedStatement)
LIFO_LINKED_LIST_DEFINE_TRAVERSE_NOQUAL(PSTracker, PreparedStatement)
/* PSTracker MEMBER FUNC DEFINITIONS AND SUPPORTING FUNCS: END */
