/* KInterbasDB Python Package - Implementation of Parameter Conversion Py->DB
 *
 * 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 _kiconversion.c,
 * without the involvement of a header file. */


/******************** FUNCTION PROTOTYPES:BEGIN ********************/
static void _complain_PyObject_to_database_field_type_mismatch(
    PyObject *py_input, char *database_field_type_name_raw,
    XSQLVAR *sqlvar, boolean is_array_element
  );

static int _try_to_accept_string_and_convert(PyObject *o, XSQLVAR *sqlvar,
    Cursor *cur
  );

static int _PyObject2XSQLVAR_check_range_SQL_INTEGER(
    unsigned short dialect,
    short data_type, short data_subtype, short scale,
    PyObject *n, PyObject *min, PyObject *max
  );

static int _PyObject2XSQLVAR_check_range_SQL_CHARACTER(PyObject *o,
    size_t actualLength, size_t maxLength
  );

boolean ISC_TIME_from_PyInt(PyObject *py_int, ISC_TIME *t);
/******************** FUNCTION PROTOTYPES:END ********************/


/******************** CONVENIENCE DEFS:BEGIN ********************/

#define TRY_TO_ACCEPT_STRING_AND_CONVERT(py_input, sqlvar, cur) \
  if (_try_to_accept_string_and_convert(py_input, sqlvar, cur) == INPUT_OK) { \
    return INPUT_OK; \
  } /* Else, do not immediately return or break. */

/* Don't allocate new memory if we're converting a database array element: */
#define ALLOC_IF_NOT_ARRAY_THEN_SET(buf_ptr, datatype, value) \
  if (!is_array_element) { \
    buf_ptr = (char *) kimem_main_malloc(sizeof(datatype)); \
    if (buf_ptr == NULL) { goto fail; } \
  } \
  /* value may contain a Python API call; we must check for a Python error \
   * after evaluating value. */ \
  { \
    datatype temp = (datatype)(value); \
    if (PyErr_Occurred()) { \
      /* Error-handling code elsewhere will take care of freeing buf_ptr, for \
       * which we allocated space just above. */ \
      goto fail; \
    } \
    *( (datatype *) buf_ptr ) = temp; \
  }

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


#define conv_in_text_conventional(py_input, sqlvar, data_type) \
  _conv_in_text( \
      FALSE, /* This is not an array element. */ \
      py_input, \
      /* For non-array-element conversion: */ \
      sqlvar, data_type, \
      /* For array-element conversion; irrelevant here: */ \
      NULL, 0, '\0' \
    )

#define conv_in_text_array(data_slot, size_of_single_element, pad_char) \
  _conv_in_text( \
      TRUE, /* This is an array element. */ \
      py_input, \
      /* For non-array-element conversion: */ \
      NULL, -1, \
      /* For array-element conversion; irrelevant here: */ \
      data_slot, size_of_single_element, pad_char \
    )


/* The _conv_in_text function should not be called except via the
 * conv_in_text_(conventional|array) macros defined above. */
static InputStatus _conv_in_text(
    /* Common: */
    boolean is_array_element,
    PyObject *py_input,
    /* For non-array-element conversion: */
    XSQLVAR *sqlvar, short data_type,
    /* For array-element conversion: */
    char **data_slot, size_t defined_field_size, char array_value_pad_char
  )
{
  if (!PyString_Check(py_input)) {
    _complain_PyObject_to_database_field_type_mismatch(py_input,
        "str", sqlvar, is_array_element
      );
    goto fail;
  }

  {
    size_t size_of_incoming_string = PyString_GET_SIZE(py_input);
    size_t max_allowed_length = (
          is_array_element
        ? defined_field_size
        : sqlvar->sqllen
      );

    /* Don't allow truncation; raise an exception if py_input is too long. */
    if (_PyObject2XSQLVAR_check_range_SQL_CHARACTER(
            py_input, size_of_incoming_string, max_allowed_length
          ) != INPUT_OK
       )
    { goto fail; }

    if (!is_array_element) {
      /* This is not an array element; we're free to use sqlvar. */
      assert (sqlvar != NULL);
      assert (data_slot == NULL);

      /* Coerce this sqlvar's type to SQL_TEXT (CHAR) so that we don't have to
       * allocate a new buffer of size
       *   sizeof(short) + size_of_incoming_string
       * just to have sizeof(short) extra bytes at the beginning to denote
       * the length of the incoming value (as we normally would with a
       * SQL_VARYING). */
      if (data_type != SQL_TEXT) {
        data_type = SQL_TEXT;
        /* Reset the XSQLVAR's type code, retaining its original null flag. */
        sqlvar->sqltype = SQL_TEXT | XSQLVAR_SQLTYPE_READ_NULL_FLAG(sqlvar);
      }

      sqlvar->sqllen = (short) size_of_incoming_string;  /* !MUST! set the
         * sqllen to prevent the database engine from bulldozing its way out
         * to the field's defined length and corrupting the value in the
         * database.
         *   The database engine assumes that an incoming CHAR buffer is sqllen
         * bytes long (sqllen is initially set to the defined length of the
         * CHAR field).  The incoming buffer might not be long enough because
         * we haven't allocated a full-sized buffer for the incoming value.
         * Instead, we're using the pre-existing, null-terminated buffer
         * inside the Python string object py_input).
         *   !Note that this XSQLVAR's original settings are later restored
         * to prevent the database client library from concluding that the
         * defined maximum length of this field is *really*
         * size_of_incoming_string, or that this field is *really* a CHAR if
         * sqltype originally indicated VARCHAR.
         *   In essence, this amounts to API abuse for the sake of a very
         * significant optimization. */
      sqlvar->sqldata = PyString_AS_STRING(py_input);
    } else {
      /* This is an array element. */
      assert (sqlvar == NULL);
      assert (data_slot != NULL);

      /* Because we don't have an XSQLVAR structure to abuse, we must actually
       * *copy* the incoming bytes into the array source buffer. */
      memcpy(*data_slot, PyString_AS_STRING(py_input), size_of_incoming_string);
      memset( (*data_slot) + size_of_incoming_string, array_value_pad_char,
          defined_field_size - size_of_incoming_string
        );
    }
  } /* end of namespace-block for size_of_incoming_string. */

  return INPUT_OK;

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



#define conv_in_internal_integer_types_conventional(py_input, sqlvar, \
    dialect, data_type, data_subtype, scale, cur \
  ) \
    _conv_in_internal_integer_types(FALSE, py_input, &sqlvar->sqldata, \
        dialect, data_type, data_subtype, scale, \
        sqlvar, cur \
      )


#define conv_in_internal_integer_types_array(py_input, data_slot, \
    dialect, data_type, data_subtype, scale, cur \
  ) \
    _conv_in_internal_integer_types(TRUE, py_input, data_slot, \
        dialect, data_type, data_subtype, scale, \
        NULL, cur \
      )

/* The _conv_in_internal_integer_types function should not be called except
 * via the _conv_in_internal_integer_types_(conventional|array) macros defined
 * above. */
static InputStatus _conv_in_internal_integer_types(
    boolean is_array_element, PyObject *py_input, char **data_slot,
    unsigned short dialect,
    short data_type, short data_subtype,
    short scale,
    XSQLVAR *sqlvar,
    Cursor *cur
  )
{
  PyObject *minN, *maxN;
  const boolean isSQLShort = (boolean) (data_type == SQL_SHORT);
  const boolean isSQLLong = (boolean) (data_type == SQL_LONG);
  const boolean isPyInt = (boolean) PyInt_Check(py_input);
  const boolean isPyLong = (boolean) PyLong_Check(py_input);

  assert (!is_array_element || sqlvar == NULL);

  if (!(isPyInt || isPyLong)) {
    if (!is_array_element) {
      TRY_TO_ACCEPT_STRING_AND_CONVERT(py_input, sqlvar, cur);
    }
    _complain_PyObject_to_database_field_type_mismatch(py_input,
        "database-internal numeric type", sqlvar, is_array_element
      );
    goto fail;
  } /* End of block that ensures that py_input is of an appropriate type. */

  /* The next step is to ensure that the scaled value is not too large for
   * storage in its internal format.  If it is not too large, we will finally
   * transfer the value from its Pythonic representation to the data_slot. */
  if (isSQLShort) {
    minN = py_SHRT_MIN;
    maxN = py_SHRT_MAX;
  } else if (isSQLLong) {
    /* On non-Windows x86_64, a SQL_LONG is actually stored as an int, not a
     * long. */
    minN = py_INT_MIN;
    maxN = py_INT_MAX;
#ifdef INTERBASE_6_OR_LATER
  } else { /* data_type must be SQL_INT64 */
    minN = py_LONG_LONG_MIN;
    maxN = py_LONG_LONG_MAX;
#endif /* INTERBASE_6_OR_LATER */
  }

  if (_PyObject2XSQLVAR_check_range_SQL_INTEGER(
          dialect,
          data_type, data_subtype, scale,
          py_input, minN, maxN
        ) != INPUT_OK
     )
  { goto fail; }

  if (isSQLShort) {
    if (isPyInt) {
      ALLOC_IF_NOT_ARRAY_THEN_SET(*data_slot, short, (short) PyInt_AS_LONG(py_input));
    } else { /* Must be PyLong */
      ALLOC_IF_NOT_ARRAY_THEN_SET(*data_slot, short, (short) PyLong_AsLong(py_input));
    }
  } else if (isSQLLong) {
    /* On non-Windows x86_64, a SQL_LONG is actually stored as an int, not a
     * long. */
    if (isPyInt) {
      ALLOC_IF_NOT_ARRAY_THEN_SET(*data_slot, int, PyInt_AS_LONG(py_input));
    } else { /* Must be PyLong */
      ALLOC_IF_NOT_ARRAY_THEN_SET(*data_slot, int, PyLong_AsLong(py_input));
    }
 #ifdef INTERBASE_6_OR_LATER
  } else { /* data_type must be SQL_INT64 */
    if (isPyInt) {
      /* There is no PyInt_AsLongLong because a PyInt's value is stored
       * internally as a C long. */
      ALLOC_IF_NOT_ARRAY_THEN_SET(*data_slot, LONG_LONG, PyInt_AS_LONG(py_input));
    } else { /* Must be PyLong */
      ALLOC_IF_NOT_ARRAY_THEN_SET(*data_slot, LONG_LONG, PyLong_AsLongLong(py_input));
    }
 #endif /* INTERBASE_6_OR_LATER */
  }

  return INPUT_OK;

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


#define _create_func_conv_in_floating(floating_type) \
  static InputStatus _conv_in_ ## floating_type ( \
      boolean is_array_element, PyObject *py_input, char **data_slot, \
      XSQLVAR *sqlvar, Cursor *cur \
    ) \
  { \
    assert (!is_array_element || sqlvar == NULL); \
    \
    if (PyFloat_Check(py_input)) { \
      ALLOC_IF_NOT_ARRAY_THEN_SET(*data_slot, floating_type, PyFloat_AS_DOUBLE(py_input)); \
    } else if (PyInt_Check(py_input)) { \
      ALLOC_IF_NOT_ARRAY_THEN_SET(*data_slot, floating_type, PyInt_AS_LONG(py_input)); \
    } else if (PyLong_Check(py_input)) { \
      ALLOC_IF_NOT_ARRAY_THEN_SET(*data_slot, floating_type, PyLong_AsLong(py_input)); \
    } else { \
      if (!is_array_element) { \
        TRY_TO_ACCEPT_STRING_AND_CONVERT(py_input, sqlvar, cur); \
      } \
      _complain_PyObject_to_database_field_type_mismatch(py_input, \
          #floating_type, sqlvar, is_array_element \
        ); \
      goto fail; \
    } \
    \
    return INPUT_OK; \
    \
    fail: \
      assert (PyErr_Occurred()); \
      return INPUT_ERROR; \
  }

/* Use a macro to create functions _conv_in_float and _conv_in_double: */
_create_func_conv_in_floating(float)
_create_func_conv_in_floating(double)

#define conv_in_float_conventional(py_input, sqlvar, cur) \
  _conv_in_float(FALSE, py_input, &sqlvar->sqldata, sqlvar, cur)

#define conv_in_float_array(py_input, data_slot, cur) \
  _conv_in_float(TRUE, py_input, data_slot, NULL, cur)

#define conv_in_double_conventional(py_input, sqlvar, cur) \
  _conv_in_double(FALSE, py_input, &sqlvar->sqldata, sqlvar, cur)

#define conv_in_double_array(py_input, data_slot, cur) \
  _conv_in_double(TRUE, py_input, data_slot, NULL, cur)


/* Date/time types: */

#define _DATETIME_INPUT_EL(index, ERROR_LABEL) \
  el = PySequence_Fast_GET_ITEM(py_input_as_tuple, index); /* borrowed ref */ \
  if (!PyInt_Check(el)) { goto ERROR_LABEL; }

#define conv_in_timestamp_conventional(py_input, sqlvar, cur) \
  _conv_in_timestamp(FALSE, py_input, &(sqlvar)->sqldata, sqlvar, cur)

#define conv_in_timestamp_array(py_input, data_slot, cur) \
  _conv_in_timestamp(TRUE, py_input, data_slot, NULL, cur)

/* The _conv_in_timestamp function should not be called except via the
 * conv_in_timestamp_(conventional|array) macros defined above. */
static InputStatus _conv_in_timestamp(
    boolean is_array_element, PyObject *py_input, char **data_slot,
    XSQLVAR *sqlvar, Cursor *cur
  )
{
  struct tm c_tm;
  PyObject *py_input_as_tuple = NULL;
  ISC_TIME microseconds;

  assert (is_array_element ?
        sqlvar == NULL
      : sqlvar != NULL && sqlvar->sqldata == NULL
    );

  /* If py_input is a string, or is a non-sequence, then it's an invalid
   * input value--unless the string happens to be a valid TIMESTAMP literal
   * that the database server will accept for implicit type conversion. */
  if (   PyString_Check(py_input) || PyUnicode_Check(py_input)
      || !PySequence_Check(py_input)
     )
  {
    if (!is_array_element) {
      TRY_TO_ACCEPT_STRING_AND_CONVERT(py_input, sqlvar, cur);
    }
    goto fail_with_type_complaint;
  } else {
    /* Only borrowed references are stored in el, so there's no need to DECREF
     * it: */
    PyObject *el = NULL;

    /* We already know that py_input is a sequence, so there's no need to pass
     * an error message to PySequence_Fast. */
    py_input_as_tuple = PySequence_Fast(py_input, "");
    if (py_input_as_tuple == NULL) { goto fail_with_type_complaint; }

    if (PySequence_Fast_GET_SIZE(py_input_as_tuple) != 7) {
      _complain_PyObject_to_database_field_type_mismatch(py_input,
          "TIMESTAMP", sqlvar, is_array_element
        );
      goto fail_with_type_complaint;
    }

    #define _TIMESTAMP_INPUT_EL(index) \
      _DATETIME_INPUT_EL(index, fail)

    _TIMESTAMP_INPUT_EL(0); c_tm.tm_year = PyInt_AS_LONG(el) - 1900;
    _TIMESTAMP_INPUT_EL(1); c_tm.tm_mon = PyInt_AS_LONG(el) - 1;
    _TIMESTAMP_INPUT_EL(2); c_tm.tm_mday = PyInt_AS_LONG(el);
    _TIMESTAMP_INPUT_EL(3); c_tm.tm_hour = PyInt_AS_LONG(el);
    _TIMESTAMP_INPUT_EL(4); c_tm.tm_min = PyInt_AS_LONG(el);
    _TIMESTAMP_INPUT_EL(5); c_tm.tm_sec = PyInt_AS_LONG(el);

    _TIMESTAMP_INPUT_EL(6);
    if (!ISC_TIME_from_PyInt(el, &microseconds)) { goto fail; }
  }

  if (!is_array_element) {
    *data_slot = (char *) kimem_main_malloc(sizeof(ISC_TIMESTAMP));
    if (*data_slot == NULL) { goto fail; }
  }
  assert (*data_slot != NULL);

  {
    ISC_TIMESTAMP *t = (ISC_TIMESTAMP *) *data_slot;
    ENTER_GDAL
    isc_encode_timestamp(&c_tm, t);
    LEAVE_GDAL
    t->timestamp_time += microseconds / 100; /* millionths -> ten-thousandths */
  }

  Py_XDECREF(py_input_as_tuple);
  return INPUT_OK;

  fail_with_type_complaint:
    _complain_PyObject_to_database_field_type_mismatch(py_input,
        "TIMESTAMP", sqlvar, is_array_element
      );
    /* Fall through to fail: */
  fail:
    assert (PyErr_Occurred());

    Py_XDECREF(py_input_as_tuple);
    if (!is_array_element && *data_slot != NULL) {
      kimem_main_free(*data_slot);
      *data_slot = NULL;
    }

    return INPUT_ERROR;
} /* _conv_in_timestamp */


#ifdef INTERBASE_6_OR_LATER


#define conv_in_date_conventional(py_input, sqlvar, cur) \
  _conv_in_date(FALSE, py_input, &sqlvar->sqldata, sqlvar, cur)

#define conv_in_date_array(py_input, data_slot, cur) \
  _conv_in_date(TRUE, py_input, data_slot, NULL, cur)

/* The _conv_in_date function should not be called except via the
 * conv_in_date_(conventional|array) macros defined above. */
static InputStatus _conv_in_date(
    boolean is_array_element, PyObject *py_input, char **data_slot,
    XSQLVAR *sqlvar, Cursor *cur
  )
{
  struct tm c_tm;
  PyObject *py_input_as_tuple = NULL;

  assert (is_array_element ?
        sqlvar == NULL
      : sqlvar != NULL && sqlvar->sqldata == NULL
    );

  /* If py_input is a string, or is a non-sequence, then it's an invalid
   * input value--unless the string happens to be a valid DATE literal
   * that the database server will accept for implicit type conversion. */
  if (   PyString_Check(py_input) || PyUnicode_Check(py_input)
      || !PySequence_Check(py_input)
     )
  {
    if (!is_array_element) {
      TRY_TO_ACCEPT_STRING_AND_CONVERT(py_input, sqlvar, cur);
    }
    goto fail_with_type_complaint;
  } else {
    /* Only borrowed references are stored in el, so there's no need to DECREF
     * it: */
    PyObject *el = NULL;

    /* We already know that py_input is a sequence, so there's no need to pass
     * an error message to PySequence_Fast. */
    py_input_as_tuple = PySequence_Fast(py_input, "");
    if (py_input_as_tuple == NULL) { goto fail_with_type_complaint; }

    if (PySequence_Fast_GET_SIZE(py_input_as_tuple) != 3) {
      goto fail_with_type_complaint;
    }

    #define _DATE_INPUT_EL(index) \
      _DATETIME_INPUT_EL(index, fail)

    _DATE_INPUT_EL(0); c_tm.tm_year = PyInt_AS_LONG(el) - 1900;
    _DATE_INPUT_EL(1); c_tm.tm_mon = PyInt_AS_LONG(el) - 1;
    _DATE_INPUT_EL(2); c_tm.tm_mday = PyInt_AS_LONG(el);
  }

  if (!is_array_element) {
    *data_slot = (char *) kimem_main_malloc(sizeof(ISC_DATE));
    if (*data_slot == NULL) { goto fail; }
  }
  assert (*data_slot != NULL);

  ENTER_GDAL
  isc_encode_sql_date(&c_tm, (ISC_DATE *) *data_slot);
  LEAVE_GDAL

  Py_XDECREF(py_input_as_tuple);
  return INPUT_OK;

  fail_with_type_complaint:
    _complain_PyObject_to_database_field_type_mismatch(py_input,
        "DATE", sqlvar, is_array_element
      );
    /* Fall through to fail: */
  fail:
    assert (PyErr_Occurred());

    Py_XDECREF(py_input_as_tuple);
    if (!is_array_element && *data_slot != NULL) {
      kimem_main_free(*data_slot);
      *data_slot = NULL;
    }

    return INPUT_ERROR;
} /* _conv_in_date */


#define conv_in_time_conventional(py_input, sqlvar, cur) \
  _conv_in_time(FALSE, py_input, &(sqlvar)->sqldata, sqlvar, cur)

#define conv_in_time_array(py_input, data_slot, cur) \
  _conv_in_time(TRUE, py_input, data_slot, NULL, cur)

/* The _conv_in_time function should not be called except via the
 * conv_in_time_(conventional|array) macros defined above. */
static InputStatus _conv_in_time(
    boolean is_array_element, PyObject *py_input, char **data_slot,
    XSQLVAR *sqlvar, Cursor *cur
  )
{
  struct tm c_tm;
  PyObject *py_input_as_tuple = NULL;
  ISC_TIME microseconds;

  assert (is_array_element ?
        sqlvar == NULL
      : sqlvar != NULL && sqlvar->sqldata == NULL
    );

  /* If py_input is a string, or is a non-sequence, then it's an invalid
   * input value--unless the string happens to be a valid TIME literal
   * that the database server will accept for implicit type conversion. */
  if (   PyString_Check(py_input) || PyUnicode_Check(py_input)
      || !PySequence_Check(py_input)
     )
  {
    if (!is_array_element) {
      TRY_TO_ACCEPT_STRING_AND_CONVERT(py_input, sqlvar, cur);
    }
    goto fail_with_type_complaint;
  } else {
    /* Only borrowed references are stored in el, so there's no need to DECREF
     * it: */
    PyObject *el = NULL;

    /* We already know that py_input is a sequence, so there's no need to pass
     * an error message to PySequence_Fast. */
    py_input_as_tuple = PySequence_Fast(py_input, "");
    if (py_input_as_tuple == NULL) { goto fail_with_type_complaint; }

    if (PySequence_Fast_GET_SIZE(py_input_as_tuple) != 4) {
      goto fail_with_type_complaint;
    }

    #define _TIME_INPUT_EL(index) \
      _DATETIME_INPUT_EL(index, fail)

    _TIME_INPUT_EL(0); c_tm.tm_hour = PyInt_AS_LONG(el);
    _TIME_INPUT_EL(1); c_tm.tm_min = PyInt_AS_LONG(el);
    _TIME_INPUT_EL(2); c_tm.tm_sec = PyInt_AS_LONG(el);

    _TIME_INPUT_EL(3);
    if (!ISC_TIME_from_PyInt(el, &microseconds)) { goto fail; }
  }

  if (!is_array_element) {
    *data_slot = (char *) kimem_main_malloc(sizeof(ISC_TIME));
    if (*data_slot == NULL) { goto fail; }
  }
  assert (*data_slot != NULL);

  {
    ISC_TIME *t = (ISC_TIME *) *data_slot;
    ENTER_GDAL
    isc_encode_sql_time(&c_tm, t);
    LEAVE_GDAL
    *t += microseconds / 100; /* millionths -> ten-thousandths */
  }

  Py_XDECREF(py_input_as_tuple);
  return INPUT_OK;

  fail_with_type_complaint:
    _complain_PyObject_to_database_field_type_mismatch(py_input,
      "TIME", sqlvar, is_array_element
    );
    /* Fall through to fail: */
  fail:
    assert (PyErr_Occurred());

    Py_XDECREF(py_input_as_tuple);
    if (!is_array_element && *data_slot != NULL) {
      kimem_main_free(*data_slot);
      *data_slot = NULL;
    }

    return INPUT_ERROR;
} /* _conv_in_time */

#endif /* INTERBASE_6_OR_LATER */


static InputStatus conv_in_blob_materialized(
    Cursor *cursor, XSQLVAR *sqlvar, PyObject *py_input
  )
{
  /* No need for overflow check here in materialized blob input because at
   * present, Python strings/buffers and database blobs have the same maximum
   * size:  2147483647 bytes. */
  ISC_STATUS *status_vector = cursor->status_vector;
  isc_db_handle db_handle = *Transaction_get_db_handle_p(cursor->trans);
  isc_tr_handle trans_handle = *Transaction_get_handle_p(cursor->trans);

  /* 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_pybuffer). */
  sqlvar->sqldata = kimem_main_malloc(sizeof(ISC_QUAD));
  if (sqlvar->sqldata == NULL) { goto fail; }

  if (PyString_Check(py_input)) {
    if (conv_in_blob_from_pystring(py_input, (ISC_QUAD *) sqlvar->sqldata,
            status_vector, db_handle, trans_handle
          ) == INPUT_ERROR
       )
    { goto fail; }
  } else if (PyBuffer_Check(py_input)) {
    if (conv_in_blob_from_pybuffer(py_input, (ISC_QUAD *) sqlvar->sqldata,
            status_vector, db_handle, trans_handle
          ) == INPUT_ERROR
       )
    { goto fail; }
  } else {
    _complain_PyObject_to_database_field_type_mismatch(py_input,
        "str", sqlvar,
        FALSE /* Arrays of blobs are not supported by the engine. */
      );
    goto fail;
  }

  return INPUT_OK;

  fail:
    assert (PyErr_Occurred());

    if (sqlvar->sqldata != NULL) {
      kimem_main_free(sqlvar->sqldata);
      sqlvar->sqldata = NULL;
    }

    return INPUT_ERROR;
} /* conv_in_blob_materialized */


#define conv_in_boolean_conventional(py_input, sqlvar) \
    _conv_in_boolean(FALSE, py_input, &(sqlvar)->sqldata)

#define conv_in_boolean_array(py_input, data_slot) \
    _conv_in_boolean(TRUE, py_input, data_slot)

static InputStatus _conv_in_boolean(boolean is_array_element,
    PyObject *py_input, char **data_slot
  )
{
  ALLOC_IF_NOT_ARRAY_THEN_SET(*data_slot,
      short, (short) PyObject_IsTrue(py_input)
    );
  return INPUT_OK;

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


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

static void _complain_PyObject_to_database_field_type_mismatch(
    PyObject *py_input, char *database_field_type_name_raw,
    XSQLVAR *sqlvar, boolean is_array_element
  )
{
  /* This function doesn't return any indicator if it failed to finish
   * successfully because it's only called if the conversion has already
   * failed; a secondary failure will change the Python exception, but won't
   * make any difference here at the C level. */
  PyObject *database_field_type_name = NULL;
  PyObject *field_name = NULL;
  PyObject *input_type = NULL;
  PyObject *input_type_repr = NULL;
  PyObject *input_repr = NULL;
  PyObject *err_msg = NULL;

  assert (py_input != NULL);
  assert (database_field_type_name_raw != NULL);
  /* If it's an array element, there's no sqlvar. */
  assert (!is_array_element || sqlvar == NULL);

  database_field_type_name = PyString_FromString(database_field_type_name_raw);
  if (database_field_type_name == NULL) { goto exit; }

  /* sqlvar->aliasname is not null-terminated. */
  field_name = (sqlvar == NULL || sqlvar->aliasname_length == 0
      ? PyString_FromString("[name not known at this stage of query execution]")
      : PyString_FromStringAndSize(sqlvar->aliasname, sqlvar->aliasname_length)
    );
  if (field_name == NULL) { goto exit; }

  input_type = PyObject_Type(py_input);
  if (input_type == NULL) { goto exit; }

  input_type_repr = PyObject_Repr(input_type);
  if (input_type_repr == NULL) { goto exit; }

  input_repr = PyObject_Repr(py_input);
  if (input_repr == NULL) { goto exit; }

  err_msg = PyString_FromFormat(
      "Error while attempting to convert object of type %s to %s for storage"
      " in %sfield %s.  The invalid input object is: %s",
      PyString_AS_STRING(input_type_repr),
      PyString_AS_STRING(database_field_type_name),
      (is_array_element ? "element of array " : ""),
      PyString_AS_STRING(field_name),
      PyString_AS_STRING(input_repr)
    );
  if (err_msg == NULL) { goto exit; }

  raise_exception(InterfaceError, PyString_AS_STRING(err_msg));

  exit:
    Py_XDECREF(database_field_type_name);
    Py_XDECREF(field_name);
    Py_XDECREF(input_type);
    Py_XDECREF(input_type_repr);
    Py_XDECREF(input_repr);
    Py_XDECREF(err_msg);
} /* _complain_PyObject_to_database_field_type_mismatch */


static InputStatus _try_to_accept_string_and_convert(
    PyObject *py_input, XSQLVAR *sqlvar, Cursor *cur
  )
{
  if (PyUnicode_Check(py_input)) {
    /* Pretend that we received a str instead of a unicode. */
    PyObject *py_str = PyUnicode_AsASCIIString(py_input);
    if (py_str == NULL) { goto fail; }

    {
      PyObject *release_list = cur->objects_to_release_after_execute;
      assert (release_list != NULL);
      {
        const int status = PyList_Append(release_list, py_str);
        /* Either the list now owns a reference to py_str, or the append call
         * failed and the list does not own a reference.  Either way, we don't
         * want to retain ownership of a reference here. */
        Py_DECREF(py_str);
        if (status != 0) { goto fail; }
      }
      /* cur->objects_to_release_after_execute owns the str created from the
       * unicode, and will make sure that it persists long enough for our
       * purposes, then will take care of releasing it. */
      py_input = py_str;
    }
  } else if (!PyString_Check(py_input)) {
    goto fail;
  }

  {
    const Py_ssize_t string_length = PyString_GET_SIZE(py_input);
    if (string_length > SHRT_MAX) { goto fail; }

    /* Reset the XSQLVAR's type code, retaining its original null flag. */
    sqlvar->sqltype = SQL_TEXT | XSQLVAR_SQLTYPE_READ_NULL_FLAG(sqlvar);
    sqlvar->sqllen = (short) string_length; /* Cast is safe; see above. */
    /* Refer to the existing buffer inside py_input; do not allocate new
     * memory. */
    sqlvar->sqldata = PyString_AS_STRING(py_input);
  }

  return INPUT_OK;

  fail:
    /* Lack of 'assert (PyErr_Occurred());' is deliberate; error code without
     * Python exception is sufficient in this case. */
    return INPUT_ERROR;
} /* _try_to_accept_string_and_convert */


static InputStatus _PyObject2XSQLVAR_check_range_SQL_CHARACTER(
    PyObject *py_s, size_t actual_length, size_t max_length
  )
{
  /* Client code should've already enforced this: */
  assert (PyString_CheckExact(py_s));

  if (actual_length > max_length) {
    /* PyString_FromFormat doesn't support the standard format code for
     * size_t, so we go through contortions: */
    PyObject *py_actual_length_long = PyLong_FromUnsignedLongLong(
        (unsigned LONG_LONG) actual_length
      );
    if (py_actual_length_long != NULL) {
      PyObject *py_max_length_long = PyLong_FromUnsignedLongLong(
          (unsigned LONG_LONG) max_length
        );
      if (py_max_length_long != NULL) {
        PyObject *py_actual_length_str = PyObject_Str(py_actual_length_long);
        if (py_actual_length_str != NULL) {
          PyObject *py_max_length_str = PyObject_Str(py_max_length_long);
          if (py_max_length_str != NULL) {
            PyObject *err_msg = PyString_FromFormat(
                "String overflow: value %s bytes long cannot fit in character"
                " field of maximum length %s (value is '%s').",
                PyString_AS_STRING(py_actual_length_str),
                PyString_AS_STRING(py_max_length_str),
                PyString_AS_STRING(py_s)
              );
            if (err_msg != NULL) {
              raise_exception_with_numeric_error_code(ProgrammingError,
                  -802, /* -802 is the IB error code for an overflow */
                  PyString_AS_STRING(err_msg)
                );
              Py_DECREF(err_msg);
            }
            Py_DECREF(py_max_length_str);
          }
          Py_DECREF(py_actual_length_str);
        }
        Py_DECREF(py_max_length_long);
      }
      Py_DECREF(py_actual_length_long);
    }
    assert (PyErr_Occurred());
    return INPUT_ERROR;
  }

  return INPUT_OK;
} /* _PyObject2XSQLVAR_check_range_SQL_CHARACTER */


static InputStatus _PyObject2XSQLVAR_check_range_SQL_INTEGER(
    unsigned short dialect,
    short data_type, short data_subtype, short scale,
    PyObject *n, PyObject *min, PyObject *max
  )
{
  assert (n != NULL);
  assert (min != NULL);
  assert (max != NULL);

  if (PyObject_Compare(n, min) < 0 || PyObject_Compare(n, max) > 0) {
    const char *external_data_type_name = get_external_data_type_name(dialect,
        data_type, data_subtype, scale
      );
    const char *internal_data_type_name = get_internal_data_type_name(data_type);

    PyObject *n_str = NULL;
    PyObject *min_str = NULL;
    PyObject *max_str = NULL;
    PyObject *err_msg = NULL;

    n_str = PyObject_Str(n);
    if (n_str == NULL) { goto exit; }

    min_str = PyObject_Str(min);
    if (min_str == NULL) { goto exit; }

    max_str = PyObject_Str(max);
    if (max_str == NULL) { goto exit; }

    err_msg = PyString_FromFormat(
        "numeric overflow: value %s (%s scaled for %d decimal places) is of"
        " too great a magnitude to fit into its internal storage type %s,"
        " which has range [%s, %s].",
        PyString_AS_STRING(n_str),
        external_data_type_name,
        abs(scale),
        internal_data_type_name,
        PyString_AS_STRING(min_str),
        PyString_AS_STRING(max_str)
      );
    if (err_msg == NULL) { goto exit; }

    raise_exception_with_numeric_error_code(ProgrammingError,
        -802, /* -802 is the IB error code for an overflow */
        PyString_AS_STRING(err_msg)
      );

    exit:
      Py_XDECREF(n_str);
      Py_XDECREF(min_str);
      Py_XDECREF(max_str);
      Py_XDECREF(err_msg);

      return INPUT_ERROR;
  }

  return INPUT_OK;
} /* _PyObject2XSQLVAR_check_range_SQL_INTEGER */

boolean ISC_TIME_from_PyInt(PyObject *py_int, ISC_TIME *t) {
  long val = PyInt_AS_LONG(py_int);
  if (val < 0 || val > UINT_MAX) {
    raise_exception(PyExc_ValueError, "Python integer intended for ISC_TIME"
        " variable does not fit."
      );
    return FALSE;
  }
  *t = (unsigned int) val;
  return TRUE;
} /* ISC_TIME_from_PyInt */

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