Created
February 27, 2014 13:51
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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" | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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