Skip to content

Commit

Permalink
Minor updates to Schemifier. Fixes lift#1038
Browse files Browse the repository at this point in the history
Add internal logging and support for structureOnly changes
  • Loading branch information
jeppenejsum committed Jun 20, 2011
1 parent ef58e9d commit 73ba486
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 24 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -46,6 +46,7 @@ project/plugins/project
# ^\.pc/ # ^\.pc/


# IntelliJ # IntelliJ
*.eml
*.iml *.iml
*.ipr *.ipr
*.iws *.iws
Expand Down
Expand Up @@ -45,12 +45,12 @@ class FieldFinder[T: ClassManifest](metaMapper: AnyRef, logger: net.liftweb.comm
val fields = Map(c.getDeclaredFields. val fields = Map(c.getDeclaredFields.
filter{f => filter{f =>
val ret = typeFilter(f.getType) val ret = typeFilter(f.getType)
logger.debug("typeFilter(" + f.getType + "); T=" + classManifest[T].erasure) logger.trace("typeFilter(" + f.getType + "); T=" + classManifest[T].erasure)
ret ret
}. }.
map(f => (deMod(f.getName), f)) :_*) map(f => (deMod(f.getName), f)) :_*)


logger.debug("fields: " + fields) logger.trace("fields: " + fields)


// this method will find all the super classes and super-interfaces // this method will find all the super classes and super-interfaces
def getAllSupers(clz: Class[_]): List[Class[_]] = clz match { def getAllSupers(clz: Class[_]): List[Class[_]] = clz match {
Expand Down
Expand Up @@ -50,12 +50,21 @@ object Schemifier extends Loggable {
def neverF(msg: => AnyRef) = {} def neverF(msg: => AnyRef) = {}




def schemify(performWrite: Boolean, logFunc: (=> AnyRef) => Unit, stables: BaseMetaMapper*): List[String] = schemify(performWrite, logFunc, DefaultConnectionIdentifier, stables :_*) def schemify(performWrite: Boolean, logFunc: (=> AnyRef) => Unit, stables: BaseMetaMapper*): List[String] =

schemify(performWrite, logFunc, DefaultConnectionIdentifier, stables :_*)
case class Collector(funcs: List[() => Any], cmds: List[String]) {
def schemify(performWrite: Boolean, logFunc: (=> AnyRef) => Unit, dbId: ConnectionIdentifier, stables: BaseMetaMapper*): List[String] =
schemify(performWrite, false, logFunc, dbId, stables :_*)

def schemify(performWrite: Boolean, structureOnly: Boolean, logFunc: (=> AnyRef) => Unit, stables: BaseMetaMapper*): List[String] =
schemify(performWrite, structureOnly, logFunc, DefaultConnectionIdentifier, stables :_*)

private case class Collector(funcs: List[() => Any], cmds: List[String]) {
def +(other: Collector) = Collector(funcs ::: other.funcs, cmds ::: other.cmds) def +(other: Collector) = Collector(funcs ::: other.funcs, cmds ::: other.cmds)
} }


private val EmptyCollector = new Collector(Nil, Nil)

private def using[RetType <: Any, VarType <: ResultSet](f: => VarType)(f2: VarType => RetType): RetType = { private def using[RetType <: Any, VarType <: ResultSet](f: => VarType)(f2: VarType => RetType): RetType = {
val theVar = f val theVar = f
try { try {
Expand All @@ -65,35 +74,61 @@ object Schemifier extends Loggable {
} }
} }


def schemify(performWrite: Boolean, logFunc: (=> AnyRef) => Unit, dbId: ConnectionIdentifier, stables: BaseMetaMapper*): List[String] = { /**
* Modify database specified in dbId so it matches the structure specified in the MetaMappers
*
* @param performWrite if false, will not write any changes to the database, only collect them
* @param structureOnly if true, will only check tables and columns, not indexes and constraints.
* Useful if schema is maintained outside Lift, but still needs structure to be in sync
* @param logFunc A function that will be called for each statement being executed if performWrite == true
* @param dbId The ConnectionIdentifier to be used
* @param stables The MetaMapper instances to check
*
* @return The list of statements needed to bring the database in a consistent state. This list is created even if performWrite=false
*/
def schemify(performWrite: Boolean, structureOnly: Boolean, logFunc: (=> AnyRef) => Unit, dbId: ConnectionIdentifier, stables: BaseMetaMapper*): List[String] = {
val tables = stables.toList val tables = stables.toList
DB.use(dbId) { con => DB.use(dbId) { con =>
// Some databases (Sybase) don't like doing transactional DDL, so we disable transactions // Some databases (Sybase) don't like doing transactional DDL, so we disable transactions
if (con.driverType.schemifierMustAutoCommit_? && !con.connection.getAutoCommit()) { if (con.driverType.schemifierMustAutoCommit_? && !con.connection.getAutoCommit()) {
con.connection.commit con.connection.commit
con.connection.setAutoCommit(true) con.connection.setAutoCommit(true)
} }
logger.debug("Starting schemify. write=%s, structureOnly=%s, dbId=%s, schema=%s, tables=%s".format(performWrite, structureOnly, dbId, getDefaultSchemaName(con), tables.map(_.dbTableName)))


val connection = con // SuperConnection(con) val connection = con // SuperConnection(con)
val driver = DriverType.calcDriver(connection) val driver = DriverType.calcDriver(connection)
val actualTableNames = new HashMap[String, String] val actualTableNames = new HashMap[String, String]
if (performWrite) tables.foreach(_.beforeSchemifier) if (performWrite) {
tables.foreach{t =>
logger.debug("Running beforeSchemifier on table %s".format(t.dbTableName))
t.beforeSchemifier
}
}

def tableCheck(t: BaseMetaMapper, desc: String, f: => Collector): Collector = {
actualTableNames.get(t._dbTableNameLC).map(x => f).getOrElse{
logger.warn("Skipping %s on table '%s' since it doesn't exist".format(desc, t.dbTableName))
EmptyCollector
}
}

val toRun = val toRun =
tables.foldLeft(Collector(Nil, Nil))((b, t) => b + ensureTable(performWrite, logFunc, t, connection, actualTableNames)) + tables.foldLeft(EmptyCollector)((b, t) => b + ensureTable(performWrite, logFunc, t, connection, actualTableNames)) +
tables.foldLeft(Collector(Nil, Nil))((b, t) => b + ensureColumns(performWrite, logFunc, t, connection, actualTableNames)) + tables.foldLeft(EmptyCollector)((b, t) => b + tableCheck(t, "ensureColumns", ensureColumns(performWrite, logFunc, t, connection, actualTableNames))) +
tables.foldLeft(Collector(Nil, Nil))((b, t) => b + ensureIndexes(performWrite, logFunc, t, connection, actualTableNames)) + (if (structureOnly)
tables.foldLeft(Collector(Nil, Nil))((b, t) => b + ensureConstraints(performWrite, logFunc, t, dbId, connection, actualTableNames)) EmptyCollector

else
/* (tables.foldLeft(EmptyCollector)((b, t) => b + tableCheck(t, "ensureIndexes", ensureIndexes(performWrite, logFunc, t, connection, actualTableNames))) +
val toRun = tables.flatMap(t => ensureTable(performWrite, t, connection, actualTableNames) ) ::: tables.foldLeft(EmptyCollector)((b, t) => b + tableCheck(t, "ensureConstraints", ensureConstraints(performWrite, logFunc, t, dbId, connection, actualTableNames)))))
tables.flatMap{t => ensureColumns(performWrite, t, connection, actualTableNames)} :::
tables.flatMap{t => ensureIndexes(performWrite, t, connection, actualTableNames)} :::
tables.flatMap{t => ensureConstraints(performWrite, t, connection, actualTableNames)}
*/


if (performWrite) { if (performWrite) {
tables.foreach(_.afterSchemifier) logger.debug("Executing DDL statements")
toRun.funcs.foreach(f => f()) toRun.funcs.foreach(f => f())
tables.foreach{t =>
logger.debug("Running afterSchemifier on table %s".format(t.dbTableName))
t.afterSchemifier
}
} }


toRun.cmds toRun.cmds
Expand Down Expand Up @@ -166,6 +201,7 @@ object Schemifier extends Loggable {
*/ */
private def maybeWrite(performWrite: Boolean, logFunc: (=> AnyRef) => Unit, connection: SuperConnection) (makeSql: () => String) : String ={ private def maybeWrite(performWrite: Boolean, logFunc: (=> AnyRef) => Unit, connection: SuperConnection) (makeSql: () => String) : String ={
val ct = makeSql() val ct = makeSql()
logger.trace("maybeWrite DDL: "+ct)
if (performWrite) { if (performWrite) {
logFunc(ct) logFunc(ct)
val st = connection.createStatement val st = connection.createStatement
Expand All @@ -176,8 +212,9 @@ object Schemifier extends Loggable {
} }


private def ensureTable(performWrite: Boolean, logFunc: (=> AnyRef) => Unit, table: BaseMetaMapper, connection: SuperConnection, actualTableNames: HashMap[String, String]): Collector = { private def ensureTable(performWrite: Boolean, logFunc: (=> AnyRef) => Unit, table: BaseMetaMapper, connection: SuperConnection, actualTableNames: HashMap[String, String]): Collector = {
val hasTable = hasTable_?(table, connection, actualTableNames) val hasTable = logger.trace("Does table exist?: "+table.dbTableName, hasTable_?(table, connection, actualTableNames))
val cmds = new ListBuffer[String]() val cmds = new ListBuffer[String]()

if (!hasTable) { if (!hasTable) {
cmds += maybeWrite(performWrite, logFunc, connection) { cmds += maybeWrite(performWrite, logFunc, connection) {
() => "CREATE TABLE "+table._dbTableNameLC+" ("+createColumns(table, connection).mkString(" , ")+") "+connection.createTablePostpend () => "CREATE TABLE "+table._dbTableNameLC+" ("+createColumns(table, connection).mkString(" , ")+") "+connection.createTablePostpend
Expand Down Expand Up @@ -219,12 +256,14 @@ object Schemifier extends Loggable {
if (tableName == table._dbTableNameLC.toLowerCase && field.dbColumnNames(field.name).map(_.toLowerCase).contains(columnName)) { if (tableName == table._dbTableNameLC.toLowerCase && field.dbColumnNames(field.name).map(_.toLowerCase).contains(columnName)) {
cols = columnName :: cols cols = columnName :: cols
hasColumn = hasColumn + 1 hasColumn = hasColumn + 1
logger.trace("Column exists: %s.%s ".format(table.dbTableName, columnName))

} }
}) })

// FIXME deal with column types // FIXME deal with column types
(field.dbColumnNames(field.name).filter(f => !cols.map(_.toLowerCase).contains(f.toLowerCase))).foreach { (field.dbColumnNames(field.name).filter(f => !cols.map(_.toLowerCase).contains(f.toLowerCase))).foreach {colName =>
colName => logger.trace("Column does not exist: %s.%s ".format(table.dbTableName, colName))

cmds += maybeWrite(performWrite, logFunc, connection) { cmds += maybeWrite(performWrite, logFunc, connection) {
() => "ALTER TABLE "+table._dbTableNameLC+" "+connection.driverType.alterAddColumn+" "+field.fieldCreatorString(connection.driverType, colName) () => "ALTER TABLE "+table._dbTableNameLC+" "+connection.driverType.alterAddColumn+" "+field.fieldCreatorString(connection.driverType, colName)
} }
Expand Down
@@ -0,0 +1,38 @@
/*
* Copyright 2011 WorldWide Conferencing, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.liftweb
package mapper

import org.specs.Specification

import common._


/**
* Systems under specification for Schemifier.
*/
object SchemifierSpec extends Specification("Schemifier Specification") {
val provider = DbProviders.H2MemoryProvider

"Schemifier" should {
"not crash in readonly if table doesn't exist" in {
provider.setupDB
Schemifier.schemify(false, Schemifier.neverF _, Thing)
}
}
}

0 comments on commit 73ba486

Please sign in to comment.