/* KInterbasDB Python Package - Implementation of Low-Level Transaction
 *                              Operations
 *
 * 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 isc_tr_handle begin_transaction(
    /* Either: */  isc_db_handle db_handle, char *tpb, Py_ssize_t tpb_len,
    /* Or: */      ISC_TEB *tebs, short teb_count,
    ISC_STATUS *status_vector
  );

static PyObject *trans___s__trans_handle;
static PyObject *trans___s__default_tpb_str_;
static PyObject *trans___s_SAVEPOINT_SPACE;
static PyObject *trans___s_ROLLBACK_TO_SPACE;
/**************************** DECLARATIONS : end *****************************/

/*********************** RAW TRANSACTION OPS : begin *************************/
static int init_kidb_transaction_support(void) {
  #define INIT_TRANS_STRING_CONST(s) \
    trans___s_ ## s = PyString_FromString(#s); \
    if (trans___s_ ## s == NULL) { goto fail; }

  INIT_TRANS_STRING_CONST(_trans_handle);
  INIT_TRANS_STRING_CONST(_default_tpb_str_);

  /* Can't use INIT_TRANS_STRING_CONST for these, because they contain
   * spaces: */
  trans___s_SAVEPOINT_SPACE = PyString_FromString("SAVEPOINT ");
  if (trans___s_SAVEPOINT_SPACE == NULL) { goto fail; }

  trans___s_ROLLBACK_TO_SPACE = PyString_FromString("ROLLBACK TO ");
  if (trans___s_ROLLBACK_TO_SPACE == NULL) { goto fail; }

  return 0;

  fail:
    return -1;
} /* init_kidb_transaction_support */

static isc_tr_handle begin_transaction(
    /* Either: */  isc_db_handle db_handle, char *tpb, Py_ssize_t tpb_len,
    /* Or: */      ISC_TEB *tebs, short teb_count,
    ISC_STATUS *status_vector
  )
{
  isc_tr_handle trans_handle = NULL_TRANS_HANDLE;

  /* (db_handle+tpb+tpb_len) and (tebs+teb_count) are mutually exclusive
   * parameters. */
  assert (
        db_handle != NULL_DB_HANDLE
      ? tebs == NULL
      : tebs != NULL && tpb == NULL
    );

  /* 2003.02.21: A huge TPB such as 'con.begin(tpb='x'*50000)' crashes the
   * FB 1.0.2 server process, but responsibly raises an error with FB 1.5b2.
   * Since kinterbasdb only exposes some 20 TPB component values, many of which
   * are mutually exclusive, I decided to impose a reasonable limit right
   * here. */
  if (tpb_len > 255) {
    raise_exception(ProgrammingError, "Transaction parameter buffer (TPB) too"
        " large.  len(tpb) must be <= 255."
      );
    goto fail;
  }

  ENTER_GDAL
  if (tebs == NULL) {
    isc_start_transaction(status_vector,
        &trans_handle,
        /* Only one database handle is being passed. */
        1, &db_handle,
        (unsigned short) tpb_len, /* Cast is safe b/c already checked val. */
        tpb
      );
  } else {
    isc_start_multiple(status_vector, &trans_handle, teb_count, tebs);
  }
  LEAVE_GDAL

  if (DB_API_ERROR(status_vector)) {
    raise_sql_exception(OperationalError, "begin transaction: ", status_vector);
    goto fail;
  }

  assert (trans_handle != NULL_TRANS_HANDLE);
  return trans_handle;
  fail:
    assert (PyErr_Occurred());
    return NULL_TRANS_HANDLE;
} /* begin_transaction */

static TransactionalOperationResult prepare_transaction(
    isc_tr_handle *trans_handle_p, ISC_STATUS *status_vector
  )
{
  assert (trans_handle_p != NULL);

  if (*trans_handle_p == NULL_TRANS_HANDLE) {
    raise_exception(ProgrammingError, "Attempted to prepare closed"
        " transaction"
      );
    return OP_RESULT_ERROR;
  }

  ENTER_GDAL
  isc_prepare_transaction(status_vector, trans_handle_p);
  LEAVE_GDAL
  if (DB_API_ERROR(status_vector)) {
    raise_sql_exception(OperationalError, "prepare: ", status_vector);
    return OP_RESULT_ERROR;
  }

  return OP_RESULT_OK;
} /* prepare_transaction */

static TransactionalOperationResult commit_transaction(
    isc_tr_handle *trans_handle_p, boolean retaining,
    ISC_STATUS *status_vector
  )
{
  assert (trans_handle_p != NULL);
  if (*trans_handle_p == NULL_TRANS_HANDLE) {
    /* As discussed on the Python DB-SIG in message:
     *   http://mail.python.org/pipermail/db-sig/2003-February/003158.html
     * , allow a transaction to be committed even if its existence is only
     * implicit. */
    return OP_RESULT_OK;
  }

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

    if (!retaining) {
      isc_commit_transaction(status_vector, trans_handle_p);
    } else {
      isc_commit_retaining(status_vector, trans_handle_p);
      assert (*trans_handle_p != NULL_TRANS_HANDLE);
    }

    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(status_vector)) {
    raise_sql_exception(OperationalError, "commit: ", status_vector);
    return OP_RESULT_ERROR;
  }

  return OP_RESULT_OK;
} /* commit_transaction */

static TransactionalOperationResult rollback_transaction(
    isc_tr_handle *trans_handle_p, boolean retaining,
    boolean allowed_to_raise, ISC_STATUS *status_vector
  )
{
  assert (trans_handle_p != NULL);
  /* If there is not an active transaction, rolling back is meaningless, but
   * acceptable. */
  if (*trans_handle_p == NULL_TRANS_HANDLE) {
    return OP_RESULT_OK;
  }

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

    if (!retaining) {
      isc_rollback_transaction(status_vector, trans_handle_p);
    } else {
      isc_rollback_retaining(status_vector, trans_handle_p);
      assert (*trans_handle_p != NULL_TRANS_HANDLE);
    }

    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(status_vector)) {
    raise_sql_exception(OperationalError, "rollback: ", status_vector);
    if (allowed_to_raise) {
      return OP_RESULT_ERROR;
    } else {
      SUPPRESS_EXCEPTION;
    }
  }

  return OP_RESULT_OK;
} /* rollback_transaction */
/************************ RAW TRANSACTION OPS : end **************************/
