Exporting Database Schema
The DDLUnit class provides API for creating and dropping database schema. It features arranging database objects in correct order (preliminary auxiliary objects, tables, constraints, auxiliary database objects) and configurable logging.
|
class DDLUnit {
protected var _schemata: Seq[Schema] = Nil
def schemata = _schemata
protected var _tables: Seq[Table[_, _]] = Nil
def tables = _tables
protected var _views: Seq[View[_, _]] = Nil
def views = _views
protected var _constraints: Seq[Constraint] = Nil
def constraints = _constraints
protected var _preAux: Seq[SchemaObject] = Nil
def preAux = _preAux
protected var _postAux: Seq[SchemaObject] = Nil
def postAux = _postAux
protected var _msgs: Seq[Msg] = Nil
def messages = _msgs
def msgsArray: Array[Msg] = messages.toArray
def this(objects: SchemaObject*) = {
this()
add(objects: _*)
}
def resetMsgs(): this.type = {
_msgs = Nil
this
}
def clear() = {
_schemata = Nil
_tables = Nil
_views = Nil
_constraints = Nil
_preAux = Nil
_postAux = Nil
resetMsgs()
}
def add(objects: SchemaObject*): this.type = {
objects.foreach(addObject(_))
this
}
def addObject(obj: SchemaObject): this.type = {
def processRelation(r: Relation[_, _]) {
addObject(r.schema)
r.preAux.foreach(o =>
if (!_preAux.contains(o)) _preAux ++= List(o))
r.postAux.foreach(o => addObject(o))
}
obj match {
case t: Table[_, _] => if (!_tables.contains(t)) {
_tables ++= List(t)
t.constraints.foreach(c => addObject(c))
t.indexes.foreach(i => addObject(i))
processRelation(t)
}
case v: View[_, _] => if (!_views.contains(v)) {
_views ++= List(v)
processRelation(v)
}
case c: Constraint => if (!_constraints.contains(c))
_constraints ++= List(c)
case s: Schema => if (!_schemata.contains(s))
_schemata ++= List(s)
case o => if (!_postAux.contains(o))
_postAux ++= List(o)
}
this
}
protected def dropObjects(objects: Seq[SchemaObject]) {
objects.reverse.foreach { o =>
tx.execute(o.sqlDrop, { st =>
st.executeUpdate()
_msgs ++= List(new Msg(
"orm.ddl.info",
"status" -> ("DROP " + o.objectName + ": OK"),
"sql" -> o.sqlDrop))
}, { e =>
_msgs ++= List(new Msg(
"orm.ddl.info",
"status" -> ("DROP " + o.objectName + ": " + e.getMessage),
"sql" -> o.sqlDrop,
"error" -> e.getMessage))
})
}
}
protected def createObjects(objects: Seq[SchemaObject]) {
objects.foreach { o =>
tx.execute(o.sqlCreate, { st =>
st.executeUpdate()
_msgs ++= List(new Msg(
"orm.ddl.info",
"status" -> ("CREATE " + o.objectName + ": OK"),
"sql" -> o.sqlCreate))
}, { e =>
_msgs ++= List(new Msg(
"orm.ddl.error",
"status" -> ("CREATE " + o.objectName + ": " + e.getMessage),
"sql" -> o.sqlCreate,
"error" -> e.getMessage))
})
}
}
def DROP(): this.type = {
resetMsgs()
_drop()
this
}
def _drop() {
tx.execute({ conn =>
// We will commit every successfull statement.
val autoCommit = conn.getAutoCommit
conn.setAutoCommit(true)
// Execute a script.
dropObjects(postAux)
dropObjects(views)
if (ormConf.dialect.supportsDropConstraints)
dropObjects(constraints)
dropObjects(tables)
dropObjects(preAux)
if (ormConf.dialect.supportsSchema)
dropObjects(schemata)
// Restore auto-commit.
conn.setAutoCommit(autoCommit)
}, { throw _ })
}
def CREATE(): this.type = {
resetMsgs()
_create()
this
}
def _create() {
tx.execute({ conn =>
// We will commit every successfull statement.
val autoCommit = conn.getAutoCommit
conn.setAutoCommit(true)
// Execute a script.
if (ormConf.dialect.supportsSchema)
createObjects(schemata)
createObjects(preAux)
createObjects(tables)
createObjects(constraints)
createObjects(views)
createObjects(postAux)
// Restore auto-commit.
conn.setAutoCommit(autoCommit)
}, { throw _ })
}
def DROP_CREATE(): this.type = {
resetMsgs()
_drop()
_create()
this
}
def close() {
tx.close()
ormConf.connectionProvider.close()
}
def objectsCount: Int = schemata.size +
tables.size +
constraints.size +
views.size +
preAux.size +
postAux.size
override def toString: String = {
var result = "Circumflex DDL Unit: "
if (messages.size == 0) {
result += objectsCount + " objects in queue."
} else {
val infoCount = messages.filter(_.key == "orm.ddl.info").size
val errorsCount = messages.filter(_.key == "orm.ddl.error").size
result += infoCount + " successful statements, " + errorsCount + " errors."
}
result
}
}
|
Building Schema from Sources
The DDLUnit singleton can inspect your compiled classes to find the relations and build schema from them. The usage is pretty simple:
DDLUnit.fromClasspath().CREATE()
You can also specify package prefix for searching (using either slashes or dots as delimiters):
DDLUnit.fromClasspath("com.myapp.model").CREATE()
By default, the compiled classes are being searched in target/classes and target/test-classes directories relative to your project's root. You can override this setting using cx.build.outputDirs configuration parameter (paths are split from String using File.pathSeparator , i.e. colon “:" in UNIX and “;" in Windows).
Actual resolving is performed using context class loader of current thread (obtained via Thread.currentThread.getContextClassLoader ) so that you can override it in any time.
|
object DDLUnit {
def outputDirs: Iterable[File] = cx.get("cx.build.outputDirs") match {
case Some(i: Iterable[File]) => i
case Some(s: String) => s.split(File.pathSeparator).map(s => new File(s))
case _ => List(new File("target/classes"), new File("target/test-classes"))
}
def fromClasspath(pkgPrefix: String = ""): DDLUnit = {
val loader = Thread.currentThread.getContextClassLoader
val ddl = new DDLUnit()
for (dir <- outputDirs) {
try {
// Resolve directories and paths
val pkgPath = pkgPrefix.replaceAll("\\.", "/")
val outUrl = dir.toURI.toURL
val pkgUrl = loader.getResource(pkgPath)
val classDir = new File(outUrl.getFile, pkgPath)
ORM_LOG.debug("Looking for schema objects in " + classDir.getAbsolutePath)
// Make sure that requisite paths exist
if (pkgUrl == null)
throw new IllegalStateException("Could not resolve package '" + pkgPath + "'")
if (!classDir.isDirectory)
throw new IllegalStateException("Class directory " + classDir.getAbsolutePath + " does not exist.")
// Iterate class files
val files = FileUtils.listFiles(classDir, Array("class"), true).asInstanceOf[java.util.Collection[File]]
for (file <- collectionAsScalaIterable(files)) {
val relPath = file.getCanonicalPath.substring(dir.getCanonicalPath.length + 1)
val className = relPath.substring(0, relPath.length - ".class".length).replaceAll(File.separator, ".")
// Ensure that anonymous objects are not processed separately.
if (className.matches("[^\\$]+(?:\\$$)?"))
try {
val c = loader.loadClass(className)
var so: SchemaObject = null
// try to treat it as a singleton
try {
val module = c.getField("MODULE$")
if (isSchemaObjectType(module.getType))
so = module.get(null).asInstanceOf[SchemaObject]
} catch {
case e: NoSuchFieldException =>
// Try to instantiate it as a POJO.
if (isSchemaObjectType(c))
so = c.newInstance.asInstanceOf[SchemaObject]
}
if (so != null) {
ddl.addObject(so)
ORM_LOG.debug("Found schema object: " + c.getName)
}
} catch {
case e: Exception =>
// Omit non-schema classes silently
}
}
} catch {
case e: Exception => ORM_LOG.warn(e.getMessage)
}
}
ORM_LOG.debug("Lookup complete, " + ddl.objectsCount + " objects found.")
ddl
}
protected def isSchemaObjectType(c: Class[_]): Boolean =
classOf[SchemaObject].isAssignableFrom(c) &&
!Modifier.isAbstract(c.getModifiers) &&
!Modifier.isInterface(c.getModifiers)
}
|