Fantom

 

//
// Copyright (c) 2008, John Sublett
// Licensed under the Academic Free License version 3.0
//
// History:
//   12 Jan 07  John Sublett  Creation
//

**
** SqlServiceTest (maybe rename from old test)
**
class SqlServiceTest : Test
{

  SqlConn? db

//////////////////////////////////////////////////////////////////////////
// Top
//////////////////////////////////////////////////////////////////////////

  Void test()
  {
    open
    try
    {
      verifyMeta
      dropTables
      createTable
      insertTable
      closures
      transactions
      preparedStmts
      executeStmts
    }
    catch (Err e)
    {
      throw e
    }
    finally
    {
      db.close
      verify(db.isClosed)
    }
  }

//////////////////////////////////////////////////////////////////////////
// Open
//////////////////////////////////////////////////////////////////////////

  Void open()
  {
    pod  := typeof.pod
    uri  := pod.config("test.uri")      ?: throw Err("Missing 'sql::test.uri' config prop")
    user := pod.config("test.username") ?: throw Err("Missing 'sql::test.username' config prop")
    pass := pod.config("test.password") ?: throw Err("Missing 'sql::test.password' config prop")
    db = SqlConn.open(uri, user, pass)
    verifyEq(db.isClosed, false)
  }

//////////////////////////////////////////////////////////////////////////
// Verify Meta
//////////////////////////////////////////////////////////////////////////

  **
  ** Drop all tables in the database
  **
  Void verifyMeta()
  {
    // call each SqlMeta no-arg method
    debug := false
    if (debug)  echo("=== SqlMeta ===")
    meta := db.meta
    meta.typeof.methods.each |m|
    {
      if (m.parent != SqlMeta#) return
      if (m.isCtor || !m.isPublic) return
      if (m.params.size > 0) return
      val := m.callOn(meta,[,])
      if (debug) echo("$m.name: " + val)
    }
  }

//////////////////////////////////////////////////////////////////////////
// Drop Tables
//////////////////////////////////////////////////////////////////////////

  **
  ** Drop all tables in the database
  **
  Void dropTables()
  {
    verifyEq(db.meta.tableExists("foo_bar_should_not_exist"), false)
    Str[] tables := db.meta.tables.dup
    while (tables.size != 0)
    {
      Int dropped := 0
      tables.each |Str tableName|
      {
        verifyEq(db.meta.tableExists(tableName), true)
        try
        {
          db.sql("drop table $tableName").execute
          tables.remove(tableName)
          dropped++
        }
        catch (Err e)
        {
          e.trace
        }
      }

      if (dropped == 0)
        throw SqlErr("All tables could not be dropped.")
    }
  }

//////////////////////////////////////////////////////////////////////////
// Create Table
//////////////////////////////////////////////////////////////////////////

  Void createTable()
  {
    db.sql(
     "create table farmers(
      farmer_id int auto_increment not null,
      name      varchar(255) not null,
      married   bit,
      pet       varchar(255),
      ss        char(4),
      age       tinyint,
      pigs      smallint,
      cows      int,
      ducks     bigint,
      height    float,
      weight    double,
      bigdec    decimal(2,1),
      dt        datetime,
      d         date,
      t         time,
      primary key (farmer_id))
      ").execute

    row := db.meta.tableRow("farmers")
    cols := row.cols

    verifyEq(cols.size, 15)
    verifyEq(cols.isRO, true)
    verifyEq(cols is Col[], true)
    verifyType(cols, Col[]#)
    verifyFarmerCols(row)

    verifyEq(row.col("foobar", false), null)
    verifyErr(ArgErr#) { row.col("foobar") }
    verifyErr(ArgErr#) { row.col("foobar", true) }
  }

//////////////////////////////////////////////////////////////////////////
// Insert Table
//////////////////////////////////////////////////////////////////////////

  Void insertTable()
  {
    // insert a couple rows
    dt := DateTime(2009, Month.dec, 15, 23, 19, 21)
    date := Date("1972-09-10")
    time := Time("14:31:55")
    data := [
      [1, "Alice",   false, "Pooh",     "abcd", 21,   1,   80,  null, 5.3f,  120f, 3.2d, dt, date, time],
      [2, "Brian",   true,  "Haley",    "1234", 35,   2,   99,   5,   5.7f,  140f, 1.5d, dt, date, time],
      [3, "Charlie", null,  "Addi",     null,   null, 3,   44,   7,   null, 6.1f,  2.0d, dt, date, time],
      [4, "Donny",   true,  null,       "wxyz", 40,  null, null, 8,   null, null,  5.0d, dt, date, time],
      [5, "John",    true,  "Berkeley", "5678", 35,  null, null, 8,   null, null,  5.7d, dt, date, time],
    ]
    data.each |Obj[] row| { insertFarmer(row[1..-1]) }

    // query
    rows := query("select * from farmers order by farmer_id")
    verifyFarmerCols(rows.first)
    verifyEq(data.size, rows.size)
    data.each |Obj[] d, Int i| { verifyRow(rows[i], d) }

    // query with type
    farmers := db.sql("select * from farmers order by farmer_id").query
    verifyType(farmers, Row[]#)
    verifyEq(farmers is Row[], true)
    verifyEq(farmers[0] is Row, true)
    f := farmers[0]
    verifyEq(f->farmer_id, 1)
    verifyEq(f->name,     "Alice")
    verifyEq(f->married,   false)
    verifyEq(f->pet,      "Pooh")
    verifyEq(f->ss,       "abcd")
    verifyEq(f->age,      21)
    verifyEq(f->pigs,     1)
    verifyEq(f->cows,     80)
    verifyEq(f->ducks,    null)
    verifyEq(f->height,   5.3f)
    verifyEq(f->weight,   120.0f)
    verifyEq(f->bigdec,   3.2d)
    verifyEq(f->dt,       dt)
    verifyEq(f->d,        date)
    verifyEq(f->t,        time)

    // mixed case
    verifyEq(f.get(f.col("name")), "Alice")
    verifyEq(f.get(f.col("Name")), "Alice")
    verifyEq(f.get(f.col("NAME")), "Alice")
    verifyEq(f->Name,  "Alice")
    verifyEq(f->NAME,  "Alice")

    verifyEq(f[f.col("pet")], "Pooh")
  }

  Void insertFarmer(Obj[] row)
  {
    s := "insert farmers (name, married, pet, ss, age, pigs, cows, ducks, height, weight, bigdec, dt, d, t) values ("
    s += row.join(", ") |Obj? o->Str|
    {
      if (o == null)     return "null"
      if (o is Str)      return "'$o'"
      if (o is DateTime) return "'" + o->toLocale("YYYY-MM-DD hh:mm:ss") + "'"
      if (o is Date)     return "'" + o->toLocale("YYYY-MM-DD") + "'"
      if (o is Time)     return "'" + o->toLocale("hh:mm:ss") + "'"
      return o.toStr
    }
    s += ")"

    // verify we got key back
    Int[] keys := execute(s)
    verifyEq(keys.size, 1)
    verifyEq(keys.typeof, Int[]#)

    // read with key and verify it is what we just wrote
    farmer := db.sql("select * from farmers where farmer_id = $keys.first").query.first
    verifyEq(farmer->name, row[0])
  }

  Void verifyFarmerCols(Row r)
  {
    verifyEq(r.cols.size, 15)
    verifyEq(r.cols.isRO, true)
    verifyCol(r.cols[0],  0,  "farmer_id", Int#,   "INT")
    verifyCol(r.cols[1],  1,  "name",      Str#,   "VARCHAR")
    verifyCol(r.cols[2],  2,  "married",   Bool#,  "BIT")
    verifyCol(r.cols[3],  3,  "pet",       Str#,   "VARCHAR")
    verifyCol(r.cols[4],  4,  "ss",        Str#,   "CHAR")
    verifyCol(r.cols[5],  5,  "age",       Int#,   "TINYINT")
    verifyCol(r.cols[6],  6,  "pigs",      Int#,   "SMALLINT")
    verifyCol(r.cols[7],  7,  "cows",      Int#,   "INT")
    verifyCol(r.cols[8],  8,  "ducks",     Int#,   "BIGINT")
    verifyCol(r.cols[9],  9,  "height",    Float#, "FLOAT")
    verifyCol(r.cols[10], 10, "weight",    Float#, "DOUBLE")
    verifyCol(r.cols[11], 11, "bigdec",    Decimal#, "DECIMAL")
    verifyCol(r.cols[12], 12, "dt",        DateTime#, "DATETIME")
    verifyCol(r.cols[13], 13, "d",         Date#,  "DATE")
    verifyCol(r.cols[14], 14, "t",         Time#,  "TIME")
  }

//////////////////////////////////////////////////////////////////////////
// Closures
//////////////////////////////////////////////////////////////////////////

  Void closures()
  {
    ages := Int?[,]
    db.sql("select age from farmers").query.each |Obj row| { ages.add(row->age) }

    ages2 := Int?[,]
    db.sql("select name, age from farmers").queryEach(null) |Obj row|
    {
      if (row->age != null)
        ages2.add((Int)row->age + 10)
      else
        ages2.add(null)
    }

    ages.each |Int? age, Int i|
    {
      if (age != null) verifyEq(age+10, ages2[i])
    }

    ages.clear
    ages2.clear
    db.sql("select age from farmers where age > 30").query.each |Obj row| { ages.add(row->age) }
    Statement stmt := db.sql("select age from farmers where age > @age").prepare
    stmt.queryEach(["age":30]) |Obj row|
    {
      if (row->age != null)
        row->age = (Int)row->age + 10
      ages2.add(row->age)
    }

    ages.each |Int? age, Int i|
    {
      if (age != null) verifyEq(age+10, ages2[i])
    }

    Int i := 0
    db.sql("select * from farmers").queryEach(null) |Row row|
    {
      if (i != 0) return
      verifyEq(row.cols.size, Farmer#.fields.size)
      Farmer#.fields.each |Field f, Int index|
      {
        //verifyEq(row.type.field(f.name), null)
        col := row.col(f.name)
        verify(col != null)
        if (f.name == "farmer_id") verifyEq(col.type, Int#)
        if (f.name == "married") verifyEq(col.type, Bool#)
        if (f.name == "pet") verifyEq(col.type, Str#)
        if (f.name == "height") verifyEq(col.type, Float#)
      }
      i++
    }
  }

//////////////////////////////////////////////////////////////////////////
// Transactions
//////////////////////////////////////////////////////////////////////////

  Void transactions()
  {
    verifyEq(db.autoCommit, true)
    db.autoCommit = false
    verifyEq(db.autoCommit, false)
    db.commit

    rows := query("select name from farmers order by name")
    verifyEq(rows.size, 5)
    verifyEq(rows[0]->name, "Alice")
    verifyEq(rows[1]->name, "Brian")
    verifyEq(rows[2]->name, "Charlie")
    verifyEq(rows[3]->name, "Donny")
    verifyEq(rows[4]->name, "John")

    insertFarmer(["Bad", false, "Bad",  "bad!", 21, 1, 80, null, 5.3f, 120f, 7.7d, DateTime.now, Date.today, Time.now])
    db.rollback
    rows = query("select name from farmers order by name")
    verifyEq(rows.size, 5)
    verifyEq(rows[0]->name, "Alice")
    verifyEq(rows[1]->name, "Brian")
    verifyEq(rows[2]->name, "Charlie")
    verifyEq(rows[3]->name, "Donny")
    verifyEq(rows[4]->name, "John")
  }

//////////////////////////////////////////////////////////////////////////
// Prepared Statements
//////////////////////////////////////////////////////////////////////////

  Void preparedStmts()
  {
    stmt := db.sql("select name, age from farmers where name = @name").prepare
    result := stmt.query(["name":"Alice"])
    verifyEq(result[0]->name, "Alice");
    result = stmt.query(["name":"Brian"])
    verifyEq(result[0]->name, "Brian");
    result = stmt.query(["name":"Charlie"])
    verifyEq(result[0]->name, "Charlie");
    result = stmt.query(["name":"Donny"])
    verifyEq(result[0]->name, "Donny");
    result = stmt.query(["name":"John"])
    verifyEq(result[0]->name, "John");
    stmt.close()

    stmt = db.sql("select name, age from farmers where age > @age").prepare
    result = stmt.query(["age":30])
    verifyEq(result.size, 3)
    result.each |Obj row| { verify(result[0]->age > 30, result[0]->age.toStr + " <= 30") }
    stmt.close()

    stmt = db.sql("select name, age from farmers where name = @name and age = @age").prepare
    result = stmt.query(["name":"John", "age":35])
    verifyEq(result.size, 1)
    verifyEq(result[0]->name, "John")
    verifyEq(result[0]->age, 35)
    stmt.close()

    stmt = db.sql("select name as x, age as y from farmers where name = @name").prepare
    result = stmt.query(["name":"Alice"])
    verifyEq(result[0]->x, "Alice")
    verifyEq(result[0]->y, 21)

    // Statement.limit
    stmt = db.sql("select name from farmers")
    verifyEq(stmt.query.size, 5)
    verifyEq(stmt.limit, null)
    stmt.limit = 3
    verifyEq(stmt.limit, 3)
    verifyEq(stmt.query.size, 3)
    stmt.limit = null
    verifyEq(stmt.limit, null)
    verifyEq(stmt.query.size, 5)
  }

//////////////////////////////////////////////////////////////////////////
// Execute statements
//////////////////////////////////////////////////////////////////////////

  Void executeStmts()
  {
    r := db.sql(Str<|update farmers set pet="Pepper" where ducks=8|>).execute
    verifyEq(r, 2)

    r = db.sql("select name, pet from farmers").execute
    verifyEq(r.typeof, Row[]#)
    rows := r as Row[]
    rows.sort |a, b| { a->name <=> b->name }
    verifyEq(rows[3]->name, "Donny"); verifyEq(rows[3]->pet, "Pepper")
    verifyEq(rows[4]->name, "John");  verifyEq(rows[4]->pet, "Pepper")
  }

//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////

  Row[] query(Str sql)
  {
    rows := (Row[])db.sql(sql).query
    // echo("  q> $sql ($rows.size rows)")
    // rows.each |Row row| { echo("     $row") }
    return rows
  }

  Obj execute(Str sql)
  {
    // echo("  q> $sql")
    return db.sql(sql).execute
  }

  Void verifyCol(Col col, Int index, Str name, Type type, Str sqlType)
  {
    verifyEq(col.index, index)
    verifyEq(col.name, name)
    verifySame(col.type, type)
    if (sqlType == "INT")
    {
      verify(col.sqlType.upper == "INT" || col.sqlType.upper == "INTEGER", col.sqlType)
    }
    else
    {
      verifyEq(col.sqlType.upper, sqlType.upper)
    }
  }

  Void verifyRow(Row r, Obj[] cells)
  {
    verifyEq(r.cols.size, cells.size)
    r.cols.each |Col c, Int i|
    {
      verifyEq(r.get(c), cells[i])
    }
  }

}

**************************************************************************
** Farmer
**************************************************************************

internal class Farmer
{
  Int farmer_id
  Str? name
  Bool married
  Str? pet
  Str? ss
  Int age
  Num? pigs
  Num? cows
  Num? ducks
  Float height
  Float weight
  Decimal? bigdec
  DateTime? dt
  Date? d
  Time? t
}