Sql type

use "ffi"
use "debug"

trait SQLType
  """
  The most portable implementation of ODBC when reading and writing across
  the various database vendors has been as textual data via a buffer.

  Given this, we have implemented our SQL Datatypes in two halves - a
  "front-end" class, and this back-end SQLType.

  This class is responsible for allocating, binding, reallocating,
  reading, and writing the buffers with the database.

  The front-end class is responsible for translating the data to and
  from the native pony datatype.

  You should only need to refer to this trait if you are implementing
  a missing SQL datatype.

  ## Implemented ODBC Types:

  * SQLBigInteger
  * SQLFloat
  * SQLInteger
  * SQLReal
  * SQLSmallInteger
  * SQLVarchar
  """

  fun \nodoc\ ref get_boxed_array(): CBoxedArray
  fun \nodoc\ ref set_boxed_array(v: CBoxedArray)

  fun \nodoc\ ref get_err(): SQLReturn val
  fun \nodoc\ ref set_err(err: SQLReturn val)

  fun \nodoc\ ref alloc(size: USize): Bool =>
    get_boxed_array().alloc(size)

  fun \nodoc\ ref null() =>
    get_boxed_array().null()

  fun \nodoc\ ref reset(): Bool =>
    """
    Zeros the buffer and resets the field that defines the length of
    data written.
    """
    get_boxed_array().reset()

  fun \nodoc\ ref string(): String iso^ ? =>
    """
    Returns the written contents of the buffer as a string
    """
    get_boxed_array().string()?

  fun \nodoc\ ref array(): Array[U8] iso^ ? =>
    """
    Returns the written contents of the buffer as an array.
    """
    get_boxed_array().array()?

  fun \nodoc\ ref _write(str: String val): Bool =>
    """
    Writes the string into the provided buffer. If the string is
    larger than the buffer then it will FAIL, not re-allocate.

    (We can't reallocate, just in case this buffer is already bound
    to a query already)
    """
    get_boxed_array().write(str)

  fun \nodoc\ ref _write_array(arr: Array[U8] val): Bool =>
    """
    Writes the string into the provided buffer. If the string is
    larger than the buffer then it will FAIL, not re-allocate.

    (We can't reallocate, just in case this buffer is already bound
    to a query already)
    """
    if (not get_boxed_array().write_array(arr)) then return false end
    true

  fun \nodoc\ ref bind_parameter(h: ODBCHandleStmt tag, col: U16): SQLReturn val =>
    if (not _bind_parameter(h, col)) then
      return get_err()
    end
    get_err()

  fun \nodoc\ ref _bind_parameter(h: ODBCHandleStmt tag, col: U16): Bool =>
    var ba: CBoxedArray = get_boxed_array()
    set_err(
      ODBCFFI.resolve(
        ODBCFFI.pSQLBindParameter_varchar(
          h,
          col,
          1,
          1,
          -1,
          ba.alloc_size.u64(),
          0,
          ba.ptr,
          ba.alloc_size.i64(),
          ba.written_size)
      )
    )
    is_success()

  fun \nodoc\ ref is_success(): Bool =>
    match get_err()
    | let x: SQLSuccess => true
    else
      false
    end

  fun \nodoc\ ref realloc_column(h: ODBCHandleStmt tag, size: USize, col: U16): SQLReturn val =>
    """
    Reallocates the buffer to be populated by the database when data
    is fetched and rebinds the new buffer to the column.
    """
    set_boxed_array(CBoxedArray .> alloc(size))
    bind_column(h, col)

  fun \nodoc\ ref bind_column(h: ODBCHandleStmt tag, col: U16): SQLReturn val =>
    if (not _bind_column(h, col)) then
      return get_err()
    end
    get_err()

  fun \nodoc\ ref _bind_column(h: ODBCHandleStmt tag, col: U16): Bool =>
    var ba: CBoxedArray = get_boxed_array()
    set_err(
      ODBCFFI.resolve(
        ODBCFFI.pSQLBindCol_varchar(h, col, 1, ba.ptr, ba.alloc_size.i64(), ba.written_size)))
    is_success()

  fun \nodoc\ ref is_null(): Bool =>
    get_boxed_array().is_null()