Row

class val Row
  """
  Immutable snapshot of one fetched row. Safe to hold across fetches,
  safe to send across actors.

  Typed accessors raise error on both out-of-range index AND type mismatch.
  """

  let _columns: Array[SqlValue] val

  new val create(columns: Array[SqlValue] iso) =>
    _columns = consume columns

  fun column(i: ColIndex): SqlValue ? =>
    """
    Polymorphic access. Raises error on out-of-range index.
    """
    let idx = (i.apply() - 1).usize()
    _columns(idx)?

  fun int(i: ColIndex): (I64 | SqlNull) ? =>
    """
    Read column as I64. Accepts any integer type, widening to I64.
    Raises error on type mismatch or out of range.
    """
    match column(i)?
    | SqlNull => SqlNull
    | let v: SqlTinyInt => v.value.i64()
    | let v: SqlSmallInt => v.value.i64()
    | let v: SqlInteger => v.value.i64()
    | let v: SqlBigInt => v.value
    else error
    end

  fun float(i: ColIndex): (F64 | SqlNull) ? =>
    """
    Read column as F64. Raises error on type mismatch or out of range.
    """
    match column(i)?
    | SqlNull => SqlNull
    | let v: SqlFloat => v.value
    else error
    end

  fun text(i: ColIndex): (String val | SqlNull) ? =>
    """
    Read column as String val. Raises error on type mismatch or out of range.
    """
    match column(i)?
    | SqlNull => SqlNull
    | let v: SqlText => v.value
    else error
    end

  fun bool(i: ColIndex): (Bool | SqlNull) ? =>
    """
    Read column as Bool. Raises error on type mismatch or out of range.

    Accepts SqlBool, any integer type (0=false, nonzero=true), and
    SqlText ("1"/"0"/"t"/"f"/"true"/"false") to handle drivers that
    report boolean columns as SMALLINT or CHAR (e.g., psqlODBC with
    BoolsAsChar=Yes).
    """
    match column(i)?
    | SqlNull => SqlNull
    | let v: SqlBool => v.value
    | let v: SqlTinyInt => v.value != 0
    | let v: SqlSmallInt => v.value != 0
    | let v: SqlInteger => v.value != 0
    | let v: SqlBigInt => v.value != 0
    | let v: SqlText =>
      match v.value.lower()
      | "1" | "t" | "true" => true
      | "0" | "f" | "false" => false
      else error
      end
    else error
    end

  fun date(i: ColIndex): (SqlDate | SqlNull) ? =>
    """
    Read column as SqlDate. Raises error on type mismatch or out of range.
    """
    match column(i)?
    | SqlNull => SqlNull
    | let v: SqlDate => v
    else error
    end

  fun time(i: ColIndex): (SqlTime | SqlNull) ? =>
    """
    Read column as SqlTime. Raises error on type mismatch or out of range.
    """
    match column(i)?
    | SqlNull => SqlNull
    | let v: SqlTime => v
    else error
    end

  fun timestamp(i: ColIndex): (SqlTimestamp | SqlNull) ? =>
    """
    Read column as SqlTimestamp. Raises error on type mismatch or out of range.
    """
    match column(i)?
    | SqlNull => SqlNull
    | let v: SqlTimestamp => v
    else error
    end

  fun decimal(i: ColIndex): (SqlDecimal | SqlNull) ? =>
    """
    Read column as SqlDecimal. Raises error on type mismatch or out of range.
    """
    match column(i)?
    | SqlNull => SqlNull
    | let v: SqlDecimal => v
    else error
    end

  fun is_null(i: ColIndex): Bool ? =>
    """
    True if column value is SQL NULL. Raises error on out of range.
    """
    match column(i)?
    | SqlNull => true
    else false
    end

  fun size(): USize =>
    """
    Number of columns in the row.
    """
    _columns.size()