All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rcumflex.circumflex-orm.3.0-RC1.source-code.ddl.scala Maven / Gradle / Ivy

The newest version!
package pro.savant.circumflex
package orm

import core._
import java.io.File
import java.lang.reflect.Modifier
import java.net.URL

/*!# 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]) {
    if (ormConf.readOnly)
      throw new CircumflexException(
        "Read-only configuration does not allow DDL statements.")
    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]) {
    if (ormConf.readOnly)
      throw new CircumflexException(
        "Read-only configuration does not allow DDL statements.")
    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

  def addPackage(urls: Iterable[URL], pkg: String) {
    add(DDLUnit.searchObjects(urls, pkg): _*)
  }

  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).
*/
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 instantiateObject(c: Class[_]): Option[SchemaObject] =
    try {
      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)
        Some(so)
      else None
    } catch {
      case e: Exception =>
        // Omit non-schema classes silently
        None
    }

  def searchObjects(urls: Iterable[URL],
                    pkgPrefix: String = ""): Seq[SchemaObject] =
    urls.toSeq
        .flatMap(url => cx.searchClasses(url, _.startsWith(pkgPrefix)))
        .distinct
        .flatMap(cl => instantiateObject(cl))

  def fromClasspath(urls: Iterable[URL],
                    pkgPrefix: String = ""): DDLUnit = {
    val ddl = new DDLUnit(searchObjects(urls, pkgPrefix): _*)
    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)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy