Skip to content

Instantly share code, notes, and snippets.

@argius
Created February 27, 2014 13:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save argius/9250414 to your computer and use it in GitHub Desktop.
Save argius/9250414 to your computer and use it in GitHub Desktop.
( See comment ) ScaffoldFromDb runs Skinny Framework's Scaffold with infos extracted from existing database tables.
/**
* ScaffoldFromDbTask - adapter of ScaffoldGenerator
*
* This task requires to add db setting files (application.conf) into classpath.
*/
object ScaffoldFromDb {
import skinny._
import skinny.task.generator._
import scalikejdbc._
import java.sql.{
Types => JavaSqlTypes,
Connection,
Statement,
ResultSet,
SQLException,
DatabaseMetaData,
ResultSetMetaData
}
def main(args: Array[String]): Unit =
if (args.length < 4) showUsage
else run(args(0), args(1), args(2), args(3))
def showUsage = println(
"Usage: (launcher) template-type target-datasource-id model-name table-name")
/**
* Runs this task.
* @param templateType template type ( no/ssp/scaml/jade, no = not generate )
* @param targetEnv Skinny Env key of target database ( development/test/staging/production )
* @param modelName model class name (for the second argument of ScaffoldGenerator)
* @param tableName table name
*/
def run(templateType: String, targetEnv: String, modelName: String, tableName: String): Unit = {
println(" *** ScaffoldFromDb ***")
val targetEnv0 = targetEnv match {
case "d" => "development"
case "t" => "test"
case "s" => "staging"
case "p" => "production"
case x => x
}
val columnInfos = extractColumnInfos(targetEnv0, tableName)
val nameAndParamTypes = columnInfos.map(SqlTypeMapping.toNameAndParamType(_))
// TODO support Using Non-Numerical or Typed Primary Key
val params: List[String] = List(tableName, modelName) ++ nameAndParamTypes.filter {
case "id:Long" | "id:Option[Long]" | "created_at:DateTime" | "updated_at:DateTime" => false
case _ => true
}
printf(s"%n params: ${params}%n%n")
templateType match {
case "no" => println(" skip generation")
case "ssp" => ScaffoldSspGenerator.run(params)
case "scaml" => ScaffoldScamlGenerator.run(params)
case "jade" => ScaffoldJadeGenerator.run(params)
case x => throw new IllegalArgumentException("unknown template type: " + x)
}
}
def extractColumnInfos(targetEnv: String, tableName: String): List[ColumnInfo] = {
using(getConnection(targetEnv)) { implicit conn =>
Seq(
(extractColumnInfosByQuery _, tableName),
(extractColumnInfosByMetaData _, tableName),
(extractColumnInfosByMetaData _, tableName.toLowerCase),
(extractColumnInfosByMetaData _, tableName.toUpperCase)).foreach { t =>
val (f, arg) = t
try {
f.apply(arg) match {
case Nil => // next
case x => return x
}
} catch {
case e: SQLException => println(" warn: " + e)
case e: Throwable => throw e
}
}
}
Nil
}
def extractColumnInfosByQuery(tableName: String)(implicit conn: Connection): List[ColumnInfo] = {
using(conn.createStatement) { stmt =>
val sql = s"SELECT * FROM ${tableName} LIMIT 0"
val m = stmt.executeQuery(sql).getMetaData
wrapIntArray(1 to m.getColumnCount toArray).map { i =>
val decimalDigitsOpt = Some(m.getScale(i))
val nullableOpt = toOptionalNullable(m.isNullable(i))
ColumnInfo(m.getColumnName(i), m.getColumnType(i),
m.getColumnTypeName(i), m.getPrecision(i), decimalDigitsOpt, nullableOpt)
} toList
}
}
def extractColumnInfosByMetaData(tableName: String)(implicit conn: Connection) //
: List[ColumnInfo] = {
val catalog = null
val schema = null
using(conn.getMetaData.getColumns(catalog, schema, tableName, null)) { rs =>
def f(infos: List[ColumnInfo]): List[ColumnInfo] =
if (rs.next) {
val decimalDigitsOpt = rs.getInt(9) match {
case _ if rs.wasNull => None
case x => Some(x)
}
val nullableOpt = rs.getInt(11) match {
case _ if rs.wasNull => None
case x => toOptionalNullable(x)
}
f(ColumnInfo(
rs.getString(4), rs.getInt(5), rs.getString(6),
rs.getInt(7), decimalDigitsOpt, nullableOpt) :: infos)
} else infos
return f(Nil).reverse
}
}
def toOptionalNullable(v: Int): Option[Boolean] = {
v match {
case 0 /* attributeNoNulls */ => Some(false)
case 1 /* attributeNullable */ => Some(true)
case 2 /* attributeNullableUnknown */ => None
case _ => None
}
}
def getConnection(targetEnv: String): Connection = {
System.setProperty(SkinnyEnv.PropertyKey, targetEnv)
DBSettings.initialize
// TODO use ConnectionPool.singleton instead of DBSettings.initialize
ConnectionPool.borrow()
}
/**
* A structure of database column info.
*/
case class ColumnInfo(
name: String, dataType: Int, typeName: String, size: Int,
decimalDigits: Option[Int], nullable: Option[Boolean])
/**
* SQL type mapping utility.
*/
object SqlTypeMapping {
import skinny.ParamType._
import JavaSqlTypes._
def toNameAndParamType(info: ColumnInfo): String = {
val paramType = toParamType(info)
val sqlTypeOpt: Option[String] = (paramType, info) match {
case (String, x) => {
val s = x.dataType match {
case _ => "varchar(" + x.size + ")"
}
Option(s)
}
// TODO except for String ( such as Number and Date )
case _ => None
}
val paramTypeString = paramType match {
case Some(x) => s"Option[$x]"
case x => x.toString
}
info.name.toLowerCase + ":" + paramTypeString + (sqlTypeOpt match {
case Some(x) => ":" + x
case _ => ""
})
}
def toParamType(info: ColumnInfo): Any = {
// TODO add custom mappings if necessary
jdbcSqlTypeToParamType(info.dataType) match {
case x if info.nullable.getOrElse(true) => x
case x => Option(x)
}
}
// TODO incomplete
def jdbcSqlTypeToParamType(dataType: Int): Any = dataType match {
case CHAR | VARCHAR | LONGVARCHAR | LONGNVARCHAR | NCHAR | NVARCHAR | CLOB | NCLOB => String
case BOOLEAN | BIT => Boolean
case TINYINT => Byte
case SMALLINT => Short
case INTEGER => Int
case BIGINT => Long
case REAL => Float
case DOUBLE | FLOAT => Double
case DECIMAL | NUMERIC => BigDecimal
case DATE => LocalDate
case TIME => LocalTime
case TIMESTAMP => DateTime
case BINARY | VARBINARY | LONGVARBINARY | BLOB => ByteArray
// NotSupported
case ARRAY => "Array"
case DATALINK => "DataLink"
case DISTINCT => "Distinct"
case JAVA_OBJECT => "JavaObject"
case NULL => "Null"
case OTHER => "Other"
case REF => "Ref"
case ROWID => "RowId"
case STRUCT => "Struct"
}
}
}
@argius
Copy link
Author

argius commented Mar 1, 2014

The improved implementation as "reverse-scaffold" was merged into 1.0.0-RC4.

https://github.com/skinny-framework/skinny-framework/releases/tag/1.0.0-RC4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment