logo

const class

haven::HavenService

sys::Obj
  sys::Thread
    sql::SqlService
      haven::HavenService
//
// Copyright (c) 2008, John Sublett
// Licensed under the Academic Free License version 3.0
//
// History:
//   05 Feb 08  John Sublett  Creation
//

using sql

**
** HavenService provides a CRUD interface for managing
** persistence of objects in a relational database.
**
** See `docLib::Haven`.
**
const class HavenService : SqlService
{
  **
  ** Make a new instance that uses the specified
  ** database for persistence.
  **
  ** - 'threadName' is the unique name used to identify the thread.
  ** - 'connection' is the connection string.  For java this is the jdbc
  **   url.  For .Net this is the connection string.
  ** - 'username' is the username for the database login.
  ** - 'password' is the password for the database login.
  ** - 'dialect' is the database specific dialect implementation.
  **
  new make(Str threadName  := null,
           Str connection  := "",
           Str username    := "",
           Str password    := "",
           Dialect dialect := null)
    : super(threadName, connection, username, password, dialect)
  {
    ns = HavenNamespace.makeFor(name)
  }

  override HavenService start()
  {
    super.start
    return this
  }

  override protected Obj run()
  {
    havenLog.info("HavenService started ($name)")
    havenLog.info("Haven connection: $connection")
    tables := Str:TableDef[:]
    loop(&process(tables))
    havenLog.info("HavenService stopped ($name)")
    return null
  }

  Obj process(Str:TableDef tables, Obj msg)
  {
    if (msg is GetTableMsg)
    {
      objType := ((GetTableMsg)msg).objType
      table := tables[objType.qname]
      if (table == null)
      {
        table = TableDef.make(this, objType)
        tables[objType.qname] = table
      }

      return table
    }
    else
      return null
  }

  **
  ** Get the table name for the specified type.
  **
  Str tableName(Type type)
  {
    return trimTableName(type.pod.name + "_" + type.name)
  }

  **
  ** Get the full name for the specified index on the specified type.
  **
  Str indexName(Type type, Str indexName)
  {
    return trimIndexName(type.pod.name + "_" + type.name + "_" + indexName)
  }

  **
  ** Does a table exist for the specified type?
  **
  Bool typeTableExists(Type objType)
  {
    open
    try
    {
      t := table(objType)
      return tableExists(t.tableName)
    }
    finally
    {
      close
    }
  }

  **
  ** Create a table for the specified type.
  **
  Void createTable(Type type)
  {
    open
    try
    {
      t := table(type)
      if (tableExists(t.tableName)) return
      ddl := t.createTableDdl
      ddl.each |Str stmt|
      {
        sql(stmt).execute
      }
    }
    finally
    {
      close
    }
  }

  **
  ** Delete the table for the specified type.
  **
  Void deleteTable(Type type)
  {
    open
    try
    {
      t := table(type)
      if (!tableExists(t.tableName)) return
      ddl := t.deleteTableDdl
      sql(ddl).execute
    }
    finally
    {
      close
    }
  }

  **
  ** Add the specified object to the database.
  **
  Uri create(Obj obj)
  {
    t := table(obj.type)
    stmt := t.prepareInsert
    try
    {
      t.execute(stmt, obj)
      t.updateAutoColumns(obj)
    }
    finally
    {
      stmt.close
    }

    return ns.objToUri(obj, this)
  }

  **
  ** List all objects of the specified type.
  **
  Obj[] listObjs(Type objType)
  {
    t := table(objType)
    stmt := t.prepareReadAllSql
    try
    {
      result := List.make(objType, 64)
      stmt.queryEach(null) |Row row|
      {
        result.add(t.fromRow(row, ns))
      }
      return (Obj[])result
    }
    finally
    {
      stmt.close
    }


  }

  **
  ** Read an object from the database by its id.
  **
  Obj read(Type objType, Obj id)
  {
    Obj obj := null
    t := table(objType)
    stmt := t.prepareReadById
    try
    {
      rowCount := 0
      stmt.queryEach(t.idParam(id)) |Row row|
      {
        rowCount++
        if (obj != null) return
        obj = t.fromRow(row, ns)
      }

      if (rowCount == 0) return null
      if (rowCount == 1) return obj
      throw HavenErr.make("Read returned multiple objects.")
    }
    finally
    {
      stmt.close
    }
  }

  **
  ** Update the specified object in the database.
  **
  Void update(Obj obj)
  {
    t := table(obj.type)
    stmt := t.prepareUpdate
    try
    {
      t.execute(stmt, obj)
    }
    finally
    {
      stmt.close
    }
  }

  **
  ** Delete the specified object from the database.
  **
  Bool delete(Obj obj)
  {
    t := table(obj.type)
    stmt := t.prepareDelete
    try
    {
      return t.execute(stmt, obj) == 1
    }
    finally
    {
      stmt.close
    }
  }

  **
  ** Delete the object with the specified id.
  **
  Bool deleteById(Type objType, Obj id)
  {
    t := table(objType)
    stmt := t.prepareDeleteById
    try
    {
      return stmt.execute(t.idParam(id)) == 1
    }
    finally
    {
      stmt.close
    }
  }

  **
  ** Delete all objects of the specified type.
  **
  Void deleteAll(Type objType)
  {
    t := table(objType)
    stmt := t.prepareDeleteAll
    try
    {
      stmt.execute
    }
    finally
    {
      stmt.close
    }
  }

//////////////////////////////////////////////////////////////////////////
// Tables
//////////////////////////////////////////////////////////////////////////

  **
  ** Get the table definition for the specified type.
  **
  internal TableDef table(Type objType)
  {
    return (TableDef)sendSync(GetTableMsg.make(objType))
  }

//////////////////////////////////////////////////////////////////////////
// Columns
//////////////////////////////////////////////////////////////////////////

  **
  ** Make a column for the specified field.  This method
  ** examines the 'field' type and invokes the appropriate
  ** type specific factory.
  **
  virtual ColDef makeColumn(Field field)
  {
    // check for an override directly on the field
    colDefType := (Type)field.facet("havenCol")
    if (colDefType != null)
      return colDefType.method("makeForField").call2(this, field)

    switch (field.of)
    {
      case Str.type:
        return StrCol.makeForField(this, field)
      case Int.type:
        return IntCol.makeForField(this, field)
      case Float.type:
        return FloatCol.makeForField(this, field)
      case Bool.type:
        return BoolCol.makeForField(this, field)
      case Uri.type:
        return UriCol.makeForField(this, field)
      case DateTime.type:
        return DateTimeCol.makeForField(this, field)
      case Buf.type:
        return BufCol.makeForField(this, field)

      default:
        if (field.of.fits(Enum.type))
          return EnumCol.makeForField(this, field)
        else if (field.of.facet("simple", false))
          return SimpleCol.makeForField(this, field)
        else
        {
          // as a last resort, try the type database
          colDefType = Type.findByFacet("havenCol", field.of).first
          if (colDefType == null)
            throw HavenErr.make("$type.qname: Unsupported type for column: $field.qname : $field.of")
          return colDefType.method("makeForField").call2(this, field)
        }
    }
  }

//////////////////////////////////////////////////////////////////////////
// Util
//////////////////////////////////////////////////////////////////////////

  **
  ** If the specified table name exceeds the maximum length for
  ** a table name in the database, mangle and trim to fit.
  **
  Str trimTableName(Str tableName)
  {
    if (tableName.size > dialect.maxTableNameLength)
      throw HavenErr.make
        ("Index name to long: $tableName > $dialect.maxTableNameLength characters.")

    return tableName
  }

  **
  ** If the specified index name exceeds the maximum length for
  ** an index name in the database, mangle and trim to fit.
  **
  Str trimIndexName(Str indexName)
  {
    if (indexName.size > dialect.maxIndexNameLength)
      throw HavenErr.make
        ("Index name to long: $indexName > $dialect.maxIndexNameLength characters.")

    return indexName
  }

//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////

  ** Standard log for haven service
  static const Log havenLog := Log.get("haven")

  **
  ** The namespace for the objects managed by this instance.
  **
  const HavenNamespace ns

}

//////////////////////////////////////////////////////////////////////////
// Messages
//////////////////////////////////////////////////////////////////////////

internal const class GetTableMsg
{
  new make(Type objType)
  {
    this.objType = objType.toImmutable
  }

  const Type objType
}