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

static int reallocate_sqlda(XSQLDA **psqlda, boolean is_input_xsqlda,
    short **sqlind_array
  )
{
  /* Returns -1 in case of error, 0 in case it did not need to do anything, and
   * 1 in case it needed to reallocate (or freshly allocate) the XSQLDA, and
   * succeeded.
   * Note that this function DOES NOT allocate space for the XSQLDA's input or
   * output buffers, only for the structure itself. */
  XSQLDA *sqlda = *psqlda;
  short required_number_of_sqlvars = -1;
  int number_of_sqlvars_previously_allocated = -1;

  /* If the XSQLDA has never been allocated (is NULL), allocate a new one and
   * return.
   * If the XSQLDA *was* previously allocated, resize it to accomodate as many
   * XSQLVARs as needed, or do nothing if it already has enough space. */
  if (sqlda == NULL) {
    required_number_of_sqlvars = INITIAL_SQLVAR_CAPACITY;
    number_of_sqlvars_previously_allocated = 0;

    sqlda = (XSQLDA *) kimem_xsqlda_malloc(
        XSQLDA_LENGTH(required_number_of_sqlvars)
      );
    if (sqlda == NULL) {
      /* Weren't calling one of the Python-supplied malloc impls; need to set
       * MemoryError. */
      PyErr_NoMemory();
      goto fail;
    }

    /* The sqld field will be set by an isc_dsql_prepare call, but in case an
     * error occurs before then, it should be initialized to zero in case it's
     * accessed by error cleanup code. */
    sqlda->sqld = 0;

    sqlda->sqln = required_number_of_sqlvars;
    sqlda->version = SQLDA_VERSION_KIDB;
    *psqlda = sqlda;

    goto sqlda_allocated_or_reallocated;
  } else if (sqlda->sqld > sqlda->sqln) {
    /* The XSQLDA needs more parameter slots (XSQLVARs) than it currently has;
     * its storage must be reallocated. */
    XSQLDA *new_sqlda;

    required_number_of_sqlvars = sqlda->sqld;
    number_of_sqlvars_previously_allocated = sqlda->sqln;

    /* Perform this error checking BEFORE the realloc call so as to avoid the
     * realloc call altogether if the caller is asking for a number of XSQLVARs
     * larger than is supported. */
    assert (required_number_of_sqlvars >= 0);
    if (required_number_of_sqlvars > MAX_XSQLVARS_IN_SQLDA) {
      PyObject *err_msg = PyString_FromFormat(
          "Statement with %d parameters exceeds maximum number of parameters"
          " supported (%d).",
          required_number_of_sqlvars, MAX_XSQLVARS_IN_SQLDA
        );
      if (err_msg == NULL) { goto fail; }
      raise_exception(ProgrammingError, PyString_AS_STRING(err_msg));
      Py_DECREF(err_msg);
      goto fail;
    }
    assert (number_of_sqlvars_previously_allocated >= 0);
    if (required_number_of_sqlvars > 0) {
      assert (
          number_of_sqlvars_previously_allocated < required_number_of_sqlvars
        );
    }

    new_sqlda = (XSQLDA *)(
        kimem_xsqlda_realloc(sqlda, XSQLDA_LENGTH(required_number_of_sqlvars))
      );
    if (new_sqlda == NULL) {
      /* We no longer set '*psqlda = NULL' in the event of a reallocation
       * memory failure, because that would prevent the cursor destructor from
       * collecting sqlind memory. */
      goto fail;
    }

    /* The reallocation succeeded, meaning that kimem_main_realloc has already
     * deallocated the old memory for us. */
    sqlda = new_sqlda;

    sqlda->sqln = required_number_of_sqlvars;
    sqlda->version = SQLDA_VERSION_KIDB;
    *psqlda = sqlda;

    goto sqlda_allocated_or_reallocated;
  }

  /* Nothing needed to be done. */
  return 0;

  sqlda_allocated_or_reallocated:
    /* We don't allocate monolithic sqlind arrays for output XSQLDAs. */
    assert (!is_input_xsqlda ? sqlind_array == NULL : TRUE);

    if (is_input_xsqlda) {
      /* Allocate memory for each XSQLVAR's NULL indicator (sqlind). */
      int i;

      short *new_sqlind_array = kimem_main_realloc(*sqlind_array,
          sizeof(short) * required_number_of_sqlvars
        );
      if (new_sqlind_array == NULL) { goto fail; }
      *sqlind_array = new_sqlind_array;

      for (i = 0; i < required_number_of_sqlvars; ++i) {
        (sqlda->sqlvar + i)->sqlind = &new_sqlind_array[i];
      }
    }

    /* Indicate enlargement even if sqlinds were not separately allocated
     * (output XSQLDAs). */
    return 1;

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

static int free_XSQLVAR_dynamically_allocated_memory(Cursor *cur) {
  /* Returns 0 if no error, -1 if error. */
  PreparedStatement *ps;
  XSQLDA *sqlda;
  assert (cur != NULL);
  ps = cur->ps_current;
  assert (ps != NULL);
  sqlda = ps->in_sqlda;

  if (sqlda != NULL) {
    XSQLVAR *sqlvar;
    int i;
    const int num_XSQLVARs = sqlda->sqld;
    assert (num_XSQLVARs >= 0 && num_XSQLVARs <= MAX_XSQLVARS_IN_SQLDA);

    for (i = 0, sqlvar = sqlda->sqlvar; i < num_XSQLVARs; i++, sqlvar++) {
      switch (XSQLVAR_SQLTYPE_IGNORING_NULL_FLAG(sqlvar)) {
        case SQL_TEXT:
          /* No new memory was allocated for SQL_TEXT values (which includes
           * not only the standard CHARs, but also VARCHARs and any value that
           * relied on implicit conversion from string). */
          break;
        default:
          if (sqlvar->sqldata != NULL) {
            kimem_main_free(sqlvar->sqldata);
            sqlvar->sqldata = NULL;
          }
      }
    }
  }

  /* If PyObject2XSQLVAR placed any objects in
   * cur->objects_to_release_after_execute to have released when
   * pyob_Cursor_execute was done, we now do so. */
  {
    PyObject *release_list = cur->objects_to_release_after_execute;
    if (release_list != NULL) {
      const Py_ssize_t list_length = PyList_GET_SIZE(release_list);
      if (list_length > 0) {
        /* The PyList_SetSlice call clears the list.  Using list_length instead
         * of list_length-1 is *not* an off-by-one error. */
        if (PyList_SetSlice(release_list, 0, list_length, NULL) != 0) {
          goto fail;
        }
      }
    }
  }

  return 0;

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

#if (defined(COMPILER_IS_MSVC_WIN32) || defined(COMPILER_IS_BCPP_WIN32))
  #define ALIGN_POINTER(ptr, alignment) \
    (((ptr) + (alignment) - 1) & ~((alignment) - 1))
#else
  #define ALIGN_POINTER(ptr, alignment) \
    (((ptr)+1) & ~1)
#endif

static char *allocate_output_buffer(XSQLDA *sqlda) {
  char *buffer;
  size_t offset, alignment, length;
  XSQLVAR *sqlvar;
  short pass, var_no, sqltype;
  short column_count = sqlda->sqld;

  /* Create a raw buffer for the output SQLDA and configure the buffer so that
   * it's ready to receive values from the database engine upon a call to
   * isc_dsql_fetch.  Instead of potentially reallocating the buffer
   * repeatedly, make an initial pass to compute the ultimate size of the
   * buffer, then come back and allocate+configure the buffer in the second
   * pass.
   * This approach is not significantly slower than the old approach for
   * single-output-value XSQLDAs, and will be vastly faster for a large class
   * of multiple-output-value-XSQLDAs, because this approach involves
   * NO REALLOCATION. */
  buffer = NULL;
  offset = 0;
  for (pass = 0; pass < 2; pass++) {
    if (pass != 0) {
      /* This is the second pass; we know the ultimate offset, so we allocate
       * the space all at once. */
      buffer = kimem_main_malloc(offset);
      if (buffer == NULL) { goto fail; }
    }

    /* Note that offset unconditionally gets reset to zero. */
    offset = 0;
    for ( sqlvar = sqlda->sqlvar, var_no = 0;
          var_no < column_count;
          sqlvar++, var_no++
        )
    {
      /* Naively, the required buffer space for an output value is equal to
       * the XSQLVAR's sqllen + sizeof(short) for the sqlind (NULL flag).
       * SQL_TEXT and SQL_VARYING are special cases, dealt with below. */
      length = alignment = sqlvar->sqllen;
      sqltype = XSQLVAR_SQLTYPE_IGNORING_NULL_FLAG(sqlvar);

      if (sqltype == SQL_TEXT) {
        alignment = 1;
      } else if (sqltype == SQL_VARYING) {
        length += sizeof(short) + 1;
        alignment = sizeof(short);
      }

      offset = ALIGN_POINTER(offset, alignment);
      if (pass != 0) {
        sqlvar->sqldata = (char *) buffer + offset;
      }
      offset += length;

      offset = ALIGN_POINTER(offset, sizeof (short));
      if (pass != 0) {
        sqlvar->sqlind = (short *) ( (char *) buffer + offset );
      }
      offset += sizeof(short);
    }

    assert (offset >= 0);
  }

  return buffer;

  fail:
    assert (PyErr_Occurred());
    return NULL;
} /* allocate_output_buffer */
