
// // 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 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#: return StrCol.makeForField(this, field) case Int#: return IntCol.makeForField(this, field) case Float#: return FloatCol.makeForField(this, field) case Bool#: return BoolCol.makeForField(this, field) case Uri#: return UriCol.makeForField(this, field) case DateTime#: return DateTimeCol.makeForField(this, field) case Buf#: return BufCol.makeForField(this, field) default: if (field.of.fits(Enum#)) 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 }