/* KInterbasDB Python Package - Implementation of Parameter Conversion
 *
 * 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>
 */

/* This source file is designed to be directly included in _kinterbasdb.c,
 * without the involvement of a header file. */

/******************** CONVENIENCE DEFS:BEGIN ********************/
#define NUMBER_OF_DECIMAL_PLACES_FROM_SCALE(scale) (-(scale))


/* Macros to determine whether a field is a fixed point field based on the
 * data_type, data_subtype, and scale.  Because the Firebird C API is a
 * monstrosity, this must be done separately for array fields. */
#ifdef INTERBASE_6_OR_LATER
  #define _DATA_TYPE_IS_INT64_CONVENTIONAL(data_type) ((data_type) == SQL_INT64)
  #define _DATA_TYPE_IS_INT64_ARRAY(data_type) ((data_type) == blr_int64)
#else
  #define _DATA_TYPE_IS_INT64_CONVENTIONAL(data_type) (FALSE)
  #define _DATA_TYPE_IS_INT64_ARRAY(data_type) (FALSE)
#endif

#define IS_FIXED_POINT__CONVENTIONAL(dialect, data_type, data_subtype, scale) \
  (boolean)(( \
   ((data_subtype) != SUBTYPE_NONE || (scale) != 0) \
   && (   (data_type) == SQL_SHORT \
       || (data_type) == SQL_LONG \
       || _DATA_TYPE_IS_INT64_CONVENTIONAL(data_type) \
      ) \
  ) || ( \
    /* Special case for fixed-point values with precisions between 10 and 18 \
     * in dialect < 3 databases, which are stored internally as double. */ \
    ((dialect) < 3 && (scale) != 0 && \
      ((data_type) == SQL_DOUBLE || (data_type) == SQL_D_FLOAT) \
    ) \
  ))

#define IS_FIXED_POINT__ARRAY_EL(dialect, data_type, data_subtype, scale) \
  (boolean)(( \
   ((data_subtype) != SUBTYPE_NONE || (scale) != 0) \
   && (   (data_type) == blr_short \
       || (data_type) == blr_long \
       || _DATA_TYPE_IS_INT64_ARRAY(data_type) \
      ) \
  ) || ( \
    /* Special case for fixed-point values with precisions between 10 and 18 \
     * in dialect < 3 databases, which are stored internally as double. */ \
    ((dialect) < 3 && (scale) != 0 && \
      ((data_type) == blr_double || (data_type) == blr_d_float) \
    ) \
  ))


#define TRY_INPUT_CONVERSION(conversion_code, label) \
  if (INPUT_OK != (conversion_code)) { goto label; }

/******************** CONVENIENCE DEFS:END ********************/

static const char *get_external_data_type_name(const unsigned short dialect,
    const short data_type, const short data_subtype, const short scale
  );
static const char *get_internal_data_type_name(short data_type);

#include "_kiconversion_type_translation.c"
#include "_kiconversion_from_db.c"
#include "_kiconversion_blob_nonstandard.c"
#include "_kiconversion_blob.c"
#include "_kiconversion_blob_streaming.c"
#include "_kiconversion_to_db.c"

#ifdef ENABLE_DB_ARRAY_SUPPORT
  #include "_kiconversion_array.c"
#endif /* ENABLE_DB_ARRAY_SUPPORT */


#define PyObject2XSQLVAR_TRY_INPUT_CONVERSION(conversion_code) \
  TRY_INPUT_CONVERSION( (conversion_code), fail );

static InputStatus PyObject2XSQLVAR(
    Cursor *cur, short sqlvar_index, XSQLVAR *sqlvar, PyObject *py_input
  )
{
  int status = INPUT_ERROR;
  PyObject *py_input_converted = NULL;

  const short data_type = XSQLVAR_SQLTYPE_IGNORING_NULL_FLAG(sqlvar);
  const short data_subtype = sqlvar->sqlsubtype;
  const short scale = sqlvar->sqlscale;
  const unsigned short dialect = Transaction_get_dialect(cur->trans);

  boolean is_nonstandard_blob = FALSE;

  assert (py_input != NULL);

  /* With input parameters, we don't know beforehand just how much space the
   * incoming value might occupy (because of implicit parameter conversion).
   * Therefore, we must allocate+free an input buffer each time we receive an
   * individual incoming parameter value, rather than preallocating an input
   * buffer for all input XSQLVARs, as kinterbasdb 3.0 did. */
  assert(sqlvar->sqldata == NULL);
  /* Space for the sqlind flag should already have been allocated in
   * reallocate_sqlda. */
  assert (sqlvar->sqlind != NULL);

  /* Give the registered dynamic type translator a chance to convert the input
   * value before it's passed to the storage code.
   * Arrays are excluded because conceptually, they're just containers.  Their
   * individual elements will be passed through the any applicable dynamic type
   * translator in kiconversion_array.c */
  if (data_type == SQL_ARRAY) {
    /* Artificially INCREF py_input_converted because it's not actually a new
     * reference returned from a converter, but we need reference-ownership-
     * symmetry with the non-array branch. */
    py_input_converted = py_input;
    Py_INCREF(py_input_converted);
  } else {
    /* Find the dynamic type translator (if any) for this field's type. */
    PyObject *translator = cursor_get_in_converter(
        cur, sqlvar_index, data_type, data_subtype, scale,
        FALSE /* not an array element */
      );
    if (translator == NULL) { goto fail; }

    is_nonstandard_blob = (boolean)
      (data_type == SQL_BLOB && PyDict_Check(translator));

    if (!is_nonstandard_blob) {
      py_input_converted = dynamically_type_convert_input_obj_if_necessary(
          py_input,
          FALSE, /* not an array element */
          dialect,
          data_type, data_subtype, scale,
          translator
        );
    } else {
      BlobMode mode;
      boolean treat_subtype_text_as_text;

      if (   validate_nonstandard_blob_config_dict(translator,
               &mode, &treat_subtype_text_as_text
             )
          != DTT_BLOB_CONFIG_VALID
         )
      { goto fail; }
      /* No other modes supported at the moment; this assertion should flag areas
       * that need to be adjusted if other modes are added in the future. */
      assert (mode == blob_mode_materialize || mode == blob_mode_stream);

      if (data_subtype == isc_blob_text && treat_subtype_text_as_text) {
        /* We've been directed to handle textual blobs as if they were VARCHAR,
         * and this is a textual blob, so use materialized mode even if we were
         * in streaming mode.  Act as though we'd executed the following
         * statement:
         *   mode = blob_mode_materialize; */
        PyObject *py_converter_override;
        PyObject *py_blob_charset_id;
        boolean is_unicode_charset;

        /* 2007.02.10: */
        /* It's a textual blob that we've been ordered to treat like normal
         * text, so we revert the is_nonstandard_blob flag to FALSE so that the
         * converted input will be passed to the normal blob handler. */
        is_nonstandard_blob = FALSE;

        if (get_blob_converter_override_for_direction(TRUE, cur, sqlvar,
                &py_converter_override, &py_blob_charset_id, &is_unicode_charset
              ) != 0
           )
        { goto fail; }
        assert (py_converter_override != NULL);
        assert (py_blob_charset_id != NULL);

        if (py_converter_override == Py_None) {
          /* There's no translator for the corresponding textual DTT slot
           * (i.e., TEXT or TEXT_UNICODE), so just INCREF the input object and
           * leave it unmodified. */
          py_input_converted = py_input;
          Py_INCREF(py_input_converted);
        } else {
          if (!is_unicode_charset) {
            /* Pass single string (py_input): */
            py_input_converted = PyObject_CallFunctionObjArgs(
                py_converter_override, py_input, NULL
              );
          } else {
            /* Pass 2-tuple of (py_input, py_blob_charset_id): */
            PyObject *tuple_of_unicode_obj_and_charset_code = PyTuple_New(2);
            if (tuple_of_unicode_obj_and_charset_code == NULL) { goto fail; }

            Py_INCREF(py_input);
            PyTuple_SET_ITEM(tuple_of_unicode_obj_and_charset_code, 0, py_input);

            Py_INCREF(py_blob_charset_id);
            PyTuple_SET_ITEM(tuple_of_unicode_obj_and_charset_code, 1,
                py_blob_charset_id
              );

            py_input_converted = PyObject_CallFunctionObjArgs(
                py_converter_override, tuple_of_unicode_obj_and_charset_code,
                NULL
              );

            Py_DECREF(tuple_of_unicode_obj_and_charset_code);
          }
        }
        assert (py_blob_charset_id != NULL);
        Py_DECREF(py_blob_charset_id);
      } else {
        /* Either it's not a textual blob, or it is a textual blob that we've
         * been ordered not to auto-[enc|de]code.
         * Mode blob_mode_stream expects py_input to be a file-like object (if
         * it isn't, an exception will be raised later).
         * Mode blob_mode_materialize with treat_subtype_text_as_text FALSE
         * functions the same way naked translation would (i.e., it passes
         * the input string/buffer through unchanged). */

        if (mode == blob_mode_materialize) {
          /* For now, materialized mode with treat_subtype_text_as_text FALSE
           * behaves the same way as "conventional" naked translation. */
          is_nonstandard_blob = FALSE;
        }

        py_input_converted = py_input;
        /* Need artificial INCREF to maintain symmetry with the cases that
         * actually create a new object via DTT: */
        Py_INCREF(py_input_converted);
      }
    }
    if (py_input_converted == NULL) { goto fail; }
  }
  /* py_input_converted is now a new reference that must be released at the end
   * of this function. */
  assert (py_input_converted != NULL);

  /* If the input Python object is None, set the input XSQLVAR to NULL whether
   * the field's definition allows NULLs or not.  This is done to give BEFORE
   * triggers a chance to act on the incoming value without premature
   * interference by kinterbasdb. */
  if (py_input_converted == Py_None) {
    if (!XSQLVAR_IS_ALLOWED_TO_BE_NULL(sqlvar)) {
      sqlvar->sqltype += 1;
      assert(XSQLVAR_IS_ALLOWED_TO_BE_NULL(sqlvar));
    }

    XSQLVAR_SET_NULL(sqlvar);
    assert (XSQLVAR_IS_NULL(sqlvar)); /* Enforce symmetry between XSQLVAR nullifying macros. */
    assert (sqlvar->sqldata == NULL); /* sqldata was null and will remain so. */
    goto succeed_allowing_null;
  }

  /* It is now certain the sqlvar will not represent a NULL value; make that
   * understanding explicit. */
  XSQLVAR_SET_NOT_NULL(sqlvar);
  assert (!XSQLVAR_IS_NULL(sqlvar)); /* Enforce symmetry between XSQLVAR nullifying macros. */

  switch (data_type) {

  case SQL_VARYING:  /* (SQL_VARYING -> VARCHAR) */
  case SQL_TEXT:     /* (SQL_TEXT    -> CHAR)    */
    PyObject2XSQLVAR_TRY_INPUT_CONVERSION(
        conv_in_text_conventional(py_input_converted, sqlvar, data_type)
      );
    break;

  case SQL_SHORT:
  case SQL_LONG:
  #ifdef INTERBASE_6_OR_LATER
  case SQL_INT64:
  #endif /* INTERBASE_6_OR_LATER */
    PyObject2XSQLVAR_TRY_INPUT_CONVERSION(
        conv_in_internal_integer_types_conventional(py_input_converted, sqlvar,
            dialect, data_type, data_subtype, scale, cur
          )
      );
    break;

  case SQL_FLOAT:
    PyObject2XSQLVAR_TRY_INPUT_CONVERSION(
        conv_in_float_conventional(py_input_converted, sqlvar, cur)
      );
    break;

  case SQL_DOUBLE:
  case SQL_D_FLOAT:
    PyObject2XSQLVAR_TRY_INPUT_CONVERSION(
        conv_in_double_conventional(py_input_converted, sqlvar, cur)
      );
    break;

  /* Handle TIMESTAMP, DATE, and TIME fields: */
  case SQL_TIMESTAMP: /* TIMESTAMP */
    PyObject2XSQLVAR_TRY_INPUT_CONVERSION(
        conv_in_timestamp_conventional(py_input_converted, sqlvar, cur)
      );
    break;

  #ifdef INTERBASE_6_OR_LATER
  case SQL_TYPE_DATE: /* DATE */
    PyObject2XSQLVAR_TRY_INPUT_CONVERSION(
        conv_in_date_conventional(py_input_converted, sqlvar, cur)
      );
    break;

  case SQL_TYPE_TIME: /* TIME */
    PyObject2XSQLVAR_TRY_INPUT_CONVERSION(
        conv_in_time_conventional(py_input_converted, sqlvar, cur)
      );
    break;
  #endif /* INTERBASE_6_OR_LATER */

  case SQL_BOOLEAN:
    PyObject2XSQLVAR_TRY_INPUT_CONVERSION(
        conv_in_boolean_conventional(py_input_converted, sqlvar)
      );
    break;

  case SQL_BLOB:
    /* 2005.06.19: Added support for non-materialized blob input. */
    if (!is_nonstandard_blob) {
      PyObject2XSQLVAR_TRY_INPUT_CONVERSION(
          conv_in_blob_materialized(cur, sqlvar, py_input_converted)
        );
    } else {
      /* Next statement allocates space for the blob's id, not for the blob's
       * contents (the contents are written segment-at-a-time in
       * conv_in_blob_from_pyfilelike). */
      sqlvar->sqldata = kimem_main_malloc(sizeof(ISC_QUAD));
      if (sqlvar->sqldata == NULL) { goto fail; }

      PyObject2XSQLVAR_TRY_INPUT_CONVERSION(
          conv_in_blob_from_pyfilelike(
              py_input_converted,
              (ISC_QUAD *) sqlvar->sqldata,
              cur->status_vector, *Transaction_get_db_handle_p(cur->trans),
              *Transaction_get_handle_p(cur->trans)
            )
        );
    }
    break;

  case SQL_ARRAY:
    #ifdef ENABLE_DB_ARRAY_SUPPORT
      /* In the SQL_ARRAY case, the input object has not actually been passed
       * through a dynamic type translator, and it never will be, because it's
       * merely a container (a Python sequence).  However, dynamic type
       * translation *is* applied to each of the input sequence's elements by
       * conv_in_array. */
      PyObject2XSQLVAR_TRY_INPUT_CONVERSION(
          conv_in_array(
              py_input_converted,
              (ISC_QUAD **) &sqlvar->sqldata,
              cur, sqlvar_index,

              sqlvar->relname, sqlvar->relname_length,
              sqlvar->sqlname, sqlvar->sqlname_length
            )
        );
    #else
      raise_exception(InternalError, "This build of kinterbasdb has database"
          " array support disabled."
        );
      goto fail;
    #endif /* ENABLE_DB_ARRAY_SUPPORT */
    break;

  default:
    raise_exception(NotSupportedError,
        "Database engine type is currently not supported."
        " " KIDB_REPORT " " KIDB_HOME_PAGE
      );
    goto fail;
  } /* end of switch */

  /* Fall through to success. */
  assert (sqlvar->sqldata != NULL);
 succeed_allowing_null: /* As in the case of py_input == Py_None. */
  status = INPUT_OK;
  goto cleanup;
 fail:
  status = INPUT_ERROR;
  if (sqlvar->sqldata != NULL) {
    kimem_main_free(sqlvar->sqldata);
    sqlvar->sqldata = NULL;
  }
 cleanup:
  if (py_input_converted != NULL && PyString_Check(py_input_converted)) {
    /* If py_input_converted is a string, sqlvar->sqldata contains only a
     * pointer to py_input_converted's internal character buffer, not a pointer
     * to a copy of the buffer.  Therefore, we must ensure that
     * py_input_converted is not garbage collected until the database engine
     * has had a chance to read its internal buffer. */
    assert(cur->objects_to_release_after_execute != NULL);
    if (PyList_Append(cur->objects_to_release_after_execute, py_input_converted) != 0) {
      status = INPUT_ERROR;
    }
    /* Decref py_input_converted so that if it was a new string object created
     * by an input-dynamic-type-translator, the only remaining reference to it
     * will then be held by cur->objects_to_release_after_execute.
     * When cur->objects_to_release_after_execute is released in
     * free_XSQLVAR_dynamically_allocated_memory (which is called at the end of
     * pyob_Cursor_execute), py_input_converted will be released as a consequence. */
  } else {
    /* If py_input_converted was any other type of *new* input-dynamic-type-
     * translator-created object than a string, it can be released right now,
     * because sqlvar->sqldata now contains an independent C-level *copy* of
     * the input value.
     * If there was no relevant dynamic type translator, py_input_converted *is*
     * py_input with an artificially incremented reference count, so it's still
     * proper to decref it here. */
  }
  Py_XDECREF(py_input_converted);

  return status;
} /* PyObject2XSQLVAR */


static const char *get_external_data_type_name(const unsigned short dialect,
    const short data_type, const short data_subtype, const short scale
  )
{
  switch (data_type) {
    case SQL_TEXT:
      return "CHAR";
    case SQL_VARYING:
      return "VARCHAR";
    case SQL_SHORT:
    case SQL_LONG:
    #ifdef INTERBASE_6_OR_LATER
    case SQL_INT64:
    #endif /* INTERBASE_6_OR_LATER */
      switch (data_subtype) {
        case SUBTYPE_NONE:
          /* The database engine doesn't always set data_subtype correctly,
           * so call IS_FIXED_POINT__CONVENTIONAL to second-guess the engine. */
          if (IS_FIXED_POINT__CONVENTIONAL(dialect, data_type, data_subtype, scale)) {
            return "NUMERIC/DECIMAL";
          } else {
            switch (data_type) {
              case SQL_SHORT:
                return "SMALLINT";
              case SQL_LONG:
                return "INTEGER";
              #ifdef INTERBASE_6_OR_LATER
              case SQL_INT64:
                return "BIGINT";
              #endif /* INTERBASE_6_OR_LATER */
            }
          }
        case SUBTYPE_NUMERIC:
          return "NUMERIC";
        case SUBTYPE_DECIMAL:
          return "DECIMAL";
      }
    case SQL_FLOAT:
      return "FLOAT";
    case SQL_DOUBLE:
    case SQL_D_FLOAT:
      return "DOUBLE";
    case SQL_TIMESTAMP:
      return "TIMESTAMP";
    #ifdef INTERBASE_6_OR_LATER
    case SQL_TYPE_DATE:
      return "DATE";
    case SQL_TYPE_TIME:
      return "TIME";
    #endif /* INTERBASE_6_OR_LATER */
    case SQL_BLOB:
      return "BLOB";
    default:
      return "UNKNOWN";
  }
} /* get_external_data_type_name */


static const char *get_internal_data_type_name(short data_type) {
  switch (data_type) {
    case SQL_TEXT:
      return "SQL_TEXT";
    case SQL_VARYING:
      return "SQL_VARYING";
    case SQL_SHORT:
      return "SQL_SHORT";
    case SQL_LONG:
      return "SQL_LONG";
    #ifdef INTERBASE_6_OR_LATER
    case SQL_INT64:
      return "SQL_INT64";
    #endif /* INTERBASE_6_OR_LATER */
    case SQL_FLOAT:
      return "SQL_FLOAT";
    case SQL_DOUBLE:
    case SQL_D_FLOAT:
      return "SQL_DOUBLE";
    case SQL_TIMESTAMP:
      return "SQL_TIMESTAMP";
    #ifdef INTERBASE_6_OR_LATER
    case SQL_TYPE_DATE:
      return "SQL_TYPE_DATE";
    case SQL_TYPE_TIME:
      return "SQL_TYPE_TIME";
    #endif /* INTERBASE_6_OR_LATER */
    case SQL_BLOB:
      return "SQL_BLOB";
    default:
      return "UNKNOWN";
  }
} /* get_internal_data_type_name */


static InputStatus convert_input_parameters(Cursor *cur, PyObject *params) {
  /* Assumption:  the type of argument $params has already been screened by
   * Cursor_execute; we know it is a sequence. */
  PreparedStatement *ps = cur->ps_current;
  XSQLDA *sqlda = ps->in_sqlda;

  int conversion_status;
  Py_ssize_t i;
  XSQLVAR *cur_sqlvar;
  OriginalXSQLVARSpecificationCache *cur_spec_cache;

  const Py_ssize_t num_required_statement_params = sqlda->sqld;

  Py_ssize_t num_supplied_statement_params = PySequence_Length(params);
  if (num_supplied_statement_params > MAX_XSQLVARS_IN_SQLDA) {
    /* These num_supplied-related contortions are here because
     * PyString_FromFormat lacks the standard %zd format code for displaying
     * Py_ssize_t values. */
    PyObject *num_supplied = PyLong_FromUnsignedLongLong(
        (unsigned LONG_LONG) num_supplied_statement_params
      );
    if (num_supplied != NULL) {
      PyObject *num_supplied_str = PyObject_Str(num_supplied);
      if (num_supplied_str != NULL) {
        PyObject *err_msg = PyString_FromFormat(
            "Statement parameter sequence contains %s parameters, but only %d"
            " are allowed.",
            PyString_AS_STRING(num_supplied_str), MAX_XSQLVARS_IN_SQLDA
          );
        if (err_msg != NULL) {
          raise_exception(ProgrammingError, PyString_AS_STRING(err_msg));
          Py_DECREF(err_msg);
        }
        Py_DECREF(num_supplied_str);
      }
      Py_DECREF(num_supplied);
    }
    goto fail;
  }

  /* For the sake of the safety of free_XSQLVAR_dynamically_allocated_memory
   * in case this function encounters a conversion error, do an initial pass
   * to set the appropriate pointers to NULL. */
  for (
      i = 0, cur_sqlvar = sqlda->sqlvar, cur_spec_cache = ps->in_var_orig_spec;
      i < num_required_statement_params;
      i++, cur_sqlvar++, cur_spec_cache++
    )
  {
    /* 2003.02.13: Was previously setting 'cur_sqlvar->sqlind = NULL;' here,
     * but that's no longer valid because sqlind is allocated in
     * reallocate_sqlda and not deallocated until Cursor_delete. */
    assert (cur_sqlvar->sqlind != NULL);

    cur_sqlvar->sqldata = NULL;

    /* Also restore the original sqlvar specification flags before attempting
     * the conversion of this input row (they would have been reset if the
     * Python object previously inbound to this XSQLVAR was implicitly
     * converted from string -> whatever DB type the field really was). */
    cur_sqlvar->sqltype = cur_spec_cache->sqltype;
    cur_sqlvar->sqllen = cur_spec_cache->sqllen;
  }

  /* This supplied-vs-required param count check must come AFTER the
   * set-all-sqlvar-pointers null loop above, so that the caller of this
   * function can safely call free_XSQLVAR_dynamically_allocated_memory in ALL
   * cases in which this function returns an error. */
  if (num_supplied_statement_params != num_required_statement_params) {
    /* This code goes through contortions to convert the Py_ssize_t variables
     * to Python longs, then build string representations of the longs, because
     * PyString_FromFormat does not support the standard %zd code for
     * displaying Py_ssize_t variables. */
    PyObject *n_req = PyLong_FromUnsignedLongLong(
        (unsigned LONG_LONG) num_required_statement_params
      );
    if (n_req != NULL) {
      PyObject *n_req_str = PyObject_Str(n_req);
      if (n_req_str != NULL) {
        PyObject *n_sup = PyLong_FromUnsignedLongLong(
            (unsigned LONG_LONG) num_supplied_statement_params
          );
        if (n_sup != NULL) {
          PyObject *n_sup_str = PyObject_Str(n_sup);
          if (n_sup_str != NULL) {
            PyObject *err_msg = PyString_FromFormat("Incorrect number of input"
                " parameters.  Expected %s; received %s.",
                PyString_AS_STRING(n_req_str), PyString_AS_STRING(n_sup_str)
              );
            if (err_msg != NULL) {
              raise_exception(ProgrammingError, PyString_AS_STRING(err_msg));
              Py_DECREF(err_msg);
            }
            Py_DECREF(n_sup_str);
          }
          Py_DECREF(n_sup);
        }
        Py_DECREF(n_req_str);
      }
      Py_DECREF(n_req);
    }
    goto fail;
  }

  for (
      i = 0, cur_sqlvar = sqlda->sqlvar;
      i < num_required_statement_params;
      i++, cur_sqlvar++
    )
  {
    PyObject *cur_param = PySequence_GetItem(params, SIZE_T_TO_PYTHON_SIZE(i));
    if (cur_param == NULL) { goto fail; }
    conversion_status = PyObject2XSQLVAR(cur,
        /* The cast from Py_ssize_t to short is safe because we've already
         * validated that there are few enough parameters: */
        (short) i,
        cur_sqlvar, cur_param
      );
    /* PySequence_GetItem returns a new reference, which must be released. */
    Py_DECREF(cur_param);
    if (conversion_status != INPUT_OK) { goto fail; }
  }

  return INPUT_OK;

  fail:
    assert (PyErr_Occurred());
    return INPUT_ERROR;
} /* convert_input_parameters */


#if (defined(ENABLE_FIELD_PRECISION_DETERMINATION) && !defined(INTERBASE_7_OR_LATER))
  #include "_kiconversion_field_precision.c"
#endif

PyObject *XSQLDA2Description(XSQLDA *sqlda, Cursor *cur) {
  /* Creates a Python DB API Cursor.description tuple from a database XSQLDA
   * and a cursor object. */
  const short sqlvar_count = sqlda->sqld;
  short sqlvar_index;
  XSQLVAR *sqlvar;

  short data_type;
  int display_size = -1;

  PyObject *py_descs_for_all_fields = NULL;
  PyObject *py_desc_for_this_field = NULL;
  PyObject *py_field_name = NULL;
  PyObject *py_type = NULL;
  PyObject *py_display_size = NULL;
  PyObject *py_internal_size = NULL;
  PyObject *py_precision = NULL;
  PyObject *py_scale = NULL;

  /* DB API Spec 2.0:  "This attribute will be None for operations that do not
   * return rows..." */
  if (sqlvar_count == 0) {
    Py_INCREF(Py_None);
    return Py_None;
  }

  py_descs_for_all_fields = PyTuple_New(sqlvar_count);
  if (py_descs_for_all_fields == NULL) { goto fail; }

  for (sqlvar_index = 0; sqlvar_index < sqlvar_count; sqlvar_index++) {
    sqlvar = sqlda->sqlvar + sqlvar_index;

    /* The length of py_desc_for_this_field is defined by the Python DB API. */
    py_desc_for_this_field = PyTuple_New(7);
    if (py_desc_for_this_field == NULL) { goto fail; }

    data_type = XSQLVAR_SQLTYPE_IGNORING_NULL_FLAG(sqlvar);

    py_internal_size = PyInt_FromLong(sqlvar->sqllen);
    if (py_internal_size == NULL) { goto fail; }

    py_scale = PyInt_FromLong(sqlvar->sqlscale);
    if (py_scale == NULL) { goto fail; }

    #ifndef ENABLE_FIELD_PRECISION_DETERMINATION
      py_precision = PyInt_FromLong(0);
    #else
      #ifdef INTERBASE_7_OR_LATER
        py_precision = PyInt_FromLong(sqlvar->sqlprecision);
      #else
        py_precision = determine_field_precision(
            ENTITY_TYPE_UNKNOWN,
            sqlvar->relname, sqlvar->relname_length,
            sqlvar->sqlname, sqlvar->sqlname_length,
            cur
          );
      #endif
    #endif
    if (py_precision == NULL) { goto fail; }

    /* Make the description's type slot adapt to dynamic type translation
     * instead of returning the same type regardless. */
    if (data_type != SQL_ARRAY) {
      PyObject *translator_key = _get_cached_type_name_for_conventional_code(
          Transaction_get_dialect(cur->trans),
          data_type, sqlvar->sqlsubtype, sqlvar->sqlscale
        );
      if (translator_key == NULL) { goto fail; }

      py_type = cursor_get_translator_output_type(cur, sqlvar_index, translator_key);
      /* If there is no registered converter for $translator_key, $py_type will
       * be NULL.  That's fine; a default will be supplied below. */
      if (py_type == NULL && PyErr_Occurred()) { goto fail; }
    }
    /* I've investigated the py_type-punning warning raised here by
     * GCC -Wall -fstrict-aliasing and concluded that the behavior that causes
     * it (casting a PyTypeObject* to a PyObject*) is unavoidable when using
     * the Python C API, but not unsafe (see the definitions in Python's
     * object.h). */
    #define DEFAULT_TYPE_IS(default_type) \
      if (py_type == NULL) { py_type = (PyObject *) &default_type; }

    switch (data_type) {
    case SQL_TEXT:
    case SQL_VARYING:
      DEFAULT_TYPE_IS(PyString_Type);
      display_size = (int) sqlvar->sqllen;
      break;

    case SQL_SHORT:
      DEFAULT_TYPE_IS(PyInt_Type);
      display_size = 6;
      break;

    case SQL_LONG:
      DEFAULT_TYPE_IS(PyInt_Type);
      display_size = 11;
      break;

    #ifdef INTERBASE_6_OR_LATER
    case SQL_INT64:
      DEFAULT_TYPE_IS(PyLong_Type);
      display_size = 20;
      break;
    #endif /* INTERBASE_6_OR_LATER */

    case SQL_DOUBLE:
    case SQL_FLOAT:
    case SQL_D_FLOAT:
      DEFAULT_TYPE_IS(PyFloat_Type);
      display_size = 17;
      break;

    case SQL_BLOB:
      /* The next statement predates DSR's involvement with kinterbasdb.  He
       * doesn't regard it as such a hot idea, but has left it alone for the
       * sake of backward compatibility. */
      Py_DECREF(py_scale);
      py_scale = PyInt_FromLong(sqlvar->sqlsubtype);
      if (py_scale == NULL) { goto fail; }

      DEFAULT_TYPE_IS(PyString_Type);
      display_size = 0;
      break;

    case SQL_TIMESTAMP:
      DEFAULT_TYPE_IS(PyTuple_Type);
      display_size = 22;
      break;

    #ifdef INTERBASE_6_OR_LATER
    case SQL_TYPE_DATE:
      DEFAULT_TYPE_IS(PyTuple_Type);
      display_size = 10;
      break;

    case SQL_TYPE_TIME:
      DEFAULT_TYPE_IS(PyTuple_Type);
      display_size = 11;
      break;
    #endif /* INTERBASE_6_OR_LATER */

    case SQL_BOOLEAN:
      DEFAULT_TYPE_IS(PyBool_Type);
      display_size = 5;
      break;

    case SQL_ARRAY:
      DEFAULT_TYPE_IS(PyList_Type);
      display_size = -1; /* Can't determine display size inexpensively. */
      break;

    default:
      /* Notice that py_type gets set to None, *not* NoneType. */
      py_type = Py_None;
      display_size = -1; /* Can't determine display size. */
    } /* end switch on data py_type */

    py_display_size = PyInt_FromLong(display_size);
    if (py_display_size == NULL) { goto fail; }

    /* If there is an alias, place the alias, rather than the real column name,
     * in the column name field of the descriptor tuple.  Before this fix, the
     * presence of an alias made no difference whatsoever in the descriptor
     * setup, and was thus inaccessible to the client programmer. */

    /* Use strncmp instead of strcmp because the sqlname fields are not
     * null-terminated. */
    if (    ( sqlvar->aliasname_length != sqlvar->sqlname_length )
         || ( strncmp(sqlvar->sqlname, sqlvar->aliasname, sqlvar->sqlname_length) != 0 )
       )
    {
      py_field_name = PyString_FromStringAndSize(
          sqlvar->aliasname, sqlvar->aliasname_length
        );
    } else {
      py_field_name = PyString_FromStringAndSize(
          sqlvar->sqlname, sqlvar->sqlname_length
        );
    }
    if (py_field_name == NULL) { goto fail; }

    assert (py_type != NULL);
    /* No Python API calls between here and the end of the loop can raise an
     * exception, so artificially INCREF py_type to allow PyTuple_SET_ITEM to
     * steal a reference to it.
     * This step should be done here because py_type can be set in so many
     * places (anywhere the DEFAULT_TYPE_IS macro is called, and elsewhere). */
    Py_INCREF(py_type);

    PyTuple_SET_ITEM( py_desc_for_this_field, 0, py_field_name );
    PyTuple_SET_ITEM( py_desc_for_this_field, 1, py_type );
    PyTuple_SET_ITEM( py_desc_for_this_field, 2, py_display_size );
    PyTuple_SET_ITEM( py_desc_for_this_field, 3, py_internal_size );
    PyTuple_SET_ITEM( py_desc_for_this_field, 4, py_precision );
    PyTuple_SET_ITEM( py_desc_for_this_field, 5, py_scale );
    PyTuple_SET_ITEM( py_desc_for_this_field, 6,
        PyBool_FromLong(XSQLVAR_SQLTYPE_READ_NULL_FLAG(sqlvar))
      );

    PyTuple_SET_ITEM(py_descs_for_all_fields, sqlvar_index, py_desc_for_this_field);

    /* Nullify intermediate PyObject pointers so that if an error arises during
     * the next iteration, the error handler at the end of the function can
     * uniformly XDECREF the pointers without worrying about whether they
     * represent references to object from the previous iteration, the
     * ownership of which has already been taken by a container. */
    py_desc_for_this_field = NULL;
    py_field_name = NULL;
    py_type = NULL;
    py_display_size = NULL;
    py_internal_size = NULL;
    py_precision = NULL;
    py_scale = NULL;
  }

  return py_descs_for_all_fields;

  fail:
    assert (PyErr_Occurred());

    Py_XDECREF(py_descs_for_all_fields);
    Py_XDECREF(py_desc_for_this_field);
    Py_XDECREF(py_field_name);
    /* py_type should NOT be DECREFed. */
    Py_XDECREF(py_display_size);
    Py_XDECREF(py_internal_size);
    Py_XDECREF(py_precision);
    Py_XDECREF(py_scale);

    return NULL;
} /* XSQLDA2Description */


static PyObject *XSQLVAR2PyObject(
    Cursor *cur, const short sqlvar_index, XSQLVAR *sqlvar
  )
{
  PyObject *result = NULL;
  PyObject *converter = NULL; /* DTT translator */

  const short scale = sqlvar->sqlscale;

  const short data_type = XSQLVAR_SQLTYPE_IGNORING_NULL_FLAG(sqlvar);
  const short data_subtype = sqlvar->sqlsubtype;
  const unsigned short dialect = Transaction_get_dialect(cur->trans);

  const boolean is_array = (boolean) (data_type == SQL_ARRAY);
  boolean is_nonstandard_blob;

  /* Array DTT is activated elsewhere--see kiconversion_array.c */
  if (!is_array) {
    converter = cursor_get_out_converter(cur, sqlvar_index,
        data_type, data_subtype, scale, FALSE
      );
    /* cursor_get_out_converter returns NULL on error; borrowed reference to
     * Py_None if there was no converter. */
    if (converter == NULL) { goto fail; }
  }

  /* 2007.02.10: */
  /* The determination of is_nonstandard_blob needs to be made before we check
   * for SQL NULL below, so that if SQL NULL is found and we jump to 'succeed',
   * the 'succeed' clause won't mistakenly try to use a standard converter for
   * a nonstandard blob: */
  is_nonstandard_blob = (data_type == SQL_BLOB && PyDict_Check(converter));

  if (   XSQLVAR_IS_ALLOWED_TO_BE_NULL(sqlvar)
      && XSQLVAR_IS_NULL(sqlvar)
     )
  {
    /* SQL NULL becomes Python None regardless of field type. */
    Py_INCREF(Py_None);
    result = Py_None;
    /* Give converters a chance to act on this value: */
    goto succeed;
  }

  /* For documentation of these data_type cases, see the IB6 API Guide
   * section entitled "SQL datatype macro constants". */
  switch (data_type) {
  /* Character data: */
  case SQL_TEXT:
    result = conv_out_char(sqlvar->sqldata, sqlvar->sqllen);
    break;

  case SQL_VARYING:
    result = conv_out_varchar(sqlvar->sqldata);
    break;

  /* Numeric data: */

  case SQL_SHORT:
  case SQL_LONG:
    result = conv_out_short_long(sqlvar->sqldata,
        data_type,
        IS_FIXED_POINT__CONVENTIONAL(dialect, data_type, data_subtype, scale),
        scale
      );
    break;

  #ifdef INTERBASE_6_OR_LATER
  case SQL_INT64:
    result = conv_out_int64(sqlvar->sqldata,
        IS_FIXED_POINT__CONVENTIONAL(dialect, data_type, data_subtype, scale),
        scale
      );
    break;
  #endif /* INTERBASE_6_OR_LATER */

  case SQL_FLOAT:
    result = conv_out_floating(*((float *) sqlvar->sqldata), dialect, scale);
    break;

  case SQL_DOUBLE:
  case SQL_D_FLOAT:
    result = conv_out_floating(*((double *) sqlvar->sqldata), dialect, scale);
    break;

  /* Date and time data: */
  case SQL_TIMESTAMP: /* TIMESTAMP */
    result = conv_out_timestamp(sqlvar->sqldata);
    break;

  #ifdef INTERBASE_6_OR_LATER
  case SQL_TYPE_DATE: /* DATE */
    result = conv_out_date(sqlvar->sqldata);
    break;

  case SQL_TYPE_TIME: /* TIME */
    result = conv_out_time(sqlvar->sqldata);
    break;
  #endif /* INTERBASE_6_OR_LATER */

  case SQL_BOOLEAN:
    result = conv_out_boolean(sqlvar->sqldata);
    break;

  case SQL_BLOB:
    /* 2005.06.21: */
    /* Special cases for blobs:
     * If treat_subtype_text_as_text is enabled for this field, that behavior
     * should override any other configured behavior, be it materialized or
     * streaming.
     *
     * Otherwise, if streaming is enabled for this field, apply the indicated
     * dynamic type translation (i.e., streaming) "prematurely", so as to avoid
     * materializing the entire blob in memory before exposing it to client
     * code via a BlobReader. */
    {
    ISC_QUAD *blob_id = (ISC_QUAD *) sqlvar->sqldata;

    if (!is_nonstandard_blob) {
      /* Backward-compatible behavior (materialized, no auto-decoding): */
      result = conv_out_blob_materialized(blob_id, cur->status_vector,
          *Transaction_get_db_handle_p(cur->trans),
          *Transaction_get_handle_p(cur->trans)
        );
    } else {
      BlobMode mode;
      boolean treat_subtype_text_as_text;
      boolean treat_subtype_text_as_text__applies;

      if (   validate_nonstandard_blob_config_dict(converter,
                 &mode, &treat_subtype_text_as_text
               )
          != DTT_BLOB_CONFIG_VALID
         )
      { goto fail; }
      /* No other modes supported at the moment; this assertion should flag areas
       * that need to be adjusted if other modes are added in the future. */
      assert (mode == blob_mode_materialize || mode == blob_mode_stream);

      treat_subtype_text_as_text__applies = (boolean)
        (data_subtype == isc_blob_text && treat_subtype_text_as_text);

      if (treat_subtype_text_as_text__applies) {
        /* We've been directed to handle textual blobs as if they were VARCHAR,
         * and this is a textual blob, so use to materialized mode even if we
         * were in streaming mode. */
        mode = blob_mode_materialize;
      }

      if (mode == blob_mode_materialize) {
        result = conv_out_blob_materialized(blob_id, cur->status_vector,
            *Transaction_get_db_handle_p(cur->trans),
            *Transaction_get_handle_p(cur->trans)
          );
        if (treat_subtype_text_as_text__applies) {
          /* Look up the character set ID of this blob to determine whether to
           * route it through the TEXT or TEXT_UNICODE dynamic type translator
           * for auto-decoding. */

          PyObject *py_converter_override;
          PyObject *py_blob_charset_id;
          boolean is_unicode_charset;

          if (get_blob_converter_override_for_direction(FALSE, cur, sqlvar,
                  &py_converter_override, &py_blob_charset_id, &is_unicode_charset
                ) != 0
             )
          { goto fail; }
          assert (py_converter_override != NULL);
          assert (py_blob_charset_id != NULL);

          if (py_converter_override == Py_None) {
            /* Return the raw result without any translation. */
          } else {
            if (!is_unicode_charset) {
              /* Pass single string (result): */
              PyObject *result_conv = PyObject_CallFunctionObjArgs(
                  py_converter_override, result, NULL
                );
              Py_DECREF(result);
              result = result_conv;
            } else {
              /* Pass 2-tuple of (result, py_blob_charset_id): */
              PyObject *tuple_of_raw_string_and_charset_code = PyTuple_New(2);
              if (tuple_of_raw_string_and_charset_code == NULL) { goto fail; }
              /* We pass reference ownership over result to the tuple. */
              PyTuple_SET_ITEM(tuple_of_raw_string_and_charset_code, 0, result);

              Py_INCREF(py_blob_charset_id);
              PyTuple_SET_ITEM(tuple_of_raw_string_and_charset_code, 1,
                  py_blob_charset_id
                );

              result = PyObject_CallFunctionObjArgs(py_converter_override,
                  tuple_of_raw_string_and_charset_code, NULL
                );

              Py_DECREF(tuple_of_raw_string_and_charset_code);
              /* Drop through to the end of the function, which will detect a
               * NULL result and indicate an error if necessary. */
            }
          }
          assert (py_blob_charset_id != NULL);
          Py_DECREF(py_blob_charset_id);
        }
      } else {
        result = (PyObject *) BlobReader_create(cur->trans);
        if (result == NULL) { goto fail; }
        if (BlobReader_open((BlobReader *) result, blob_id) != 0) { goto fail; }
        assert (BlobReader_is_open((BlobReader *) result));
      }
    }
    }
    break;

  case SQL_ARRAY:
    #ifdef ENABLE_DB_ARRAY_SUPPORT
      result = conv_out_array(
          cur, sqlvar_index,
          (ISC_QUAD *) sqlvar->sqldata,

          cur->status_vector,
          Transaction_get_db_handle_p(cur->trans),
          Transaction_get_handle_p(cur->trans),

          sqlvar->relname, sqlvar->relname_length,
          sqlvar->sqlname, sqlvar->sqlname_length
        );
    #else
      raise_exception(InternalError, "This build of kinterbasdb has database"
          " array support disabled."
        );
      goto fail;
    #endif /* ENABLE_DB_ARRAY_SUPPORT */
    break;

  default:
    raise_exception( NotSupportedError,
        "Outgoing conversion of type not supported."
        " " KIDB_REPORT " " KIDB_HOME_PAGE
      );
    return NULL;
  }

  if (result == NULL) { goto fail; }

  succeed:
    /* Nonstandard blobs and arrays are subject to special dynamic type
     * translation, which is not applied in the standard manner. */
    assert (result != NULL);
    if (!(is_nonstandard_blob || is_array)) {
      assert (converter != NULL); /* Can't be NULL, but may be None. */
      /* Replacing the PyObject pointer in result is *not* a refcount leak; see
       * the comments in dynamically_type_convert_output_obj_if_necessary. */
      result = dynamically_type_convert_output_obj_if_necessary(
          result, converter, data_type, data_subtype
        );
    }
    return result;

  fail:
    Py_XDECREF(result);
    return NULL;
} /* XSQLVAR2PyObject */


PyObject *XSQLDA2Tuple(Cursor *cur, XSQLDA *sqlda) {
  const short sqlvar_count = sqlda->sqld;
  short sqlvar_index;
  PyObject *var;

  PyObject *record = PyTuple_New(sqlvar_count);
  if (record == NULL) { return NULL; }

  for (sqlvar_index = 0; sqlvar_index < sqlvar_count; ++sqlvar_index) {
    var = XSQLVAR2PyObject(cur, sqlvar_index, sqlda->sqlvar + sqlvar_index);
    if (var == NULL) {
      /* XSQLVAR2PyObject will have set an exception. */
      goto fail;
    }
    /* PyTuple_SET_ITEM steals our ref to var. */
    PyTuple_SET_ITEM(record, sqlvar_index, var);
  }

  return record;

  fail:
    assert (PyErr_Occurred());
    Py_XDECREF(record);
    return NULL;
} /* XSQLDA2Tuple */
