forked from playframework/play-slick
-
Notifications
You must be signed in to change notification settings - Fork 7
/
SlickPlugin.scala
118 lines (109 loc) · 4.27 KB
/
SlickPlugin.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package play.api.db.slick
import play.api.Application
import play.api.Plugin
import play.libs.ReflectionsCache
import org.reflections.scanners.TypesScanner
import scala.slick.session.Database
import play.api.libs.Files
import play.api.Mode
import scala.slick.lifted.DDL
import play.api.PlayException
trait AutoDDLInterface{
/** A map from play datasource name to slick table objects, for which an evolution with DDL statements should be autogenerated.
*/
def tables : Map[String,Seq[slick.driver.BasicTableComponent#Table[_]]]
}
class SlickDDLPlugin(app: Application) extends Plugin {
private val CreatedBy = "# --- Created by Slick DDL"
private val configKey = "slick.autoddl_dbs"
def confError(msg:String,e:Option[Throwable]=None) = app.configuration.reportError(configKey, msg, e)
override def onStart(): Unit = {
app.configuration
.getString(configKey)
.map( _.split(",").map(_.trim).filter(_ != "") ) // remove whitespace and empty db names
.foreach{
_.foreach{ db =>
if (app.mode != Mode.Prod) {
val dir = "conf/evolutions/" + db
val fileName = dir + "/1.sql"
val file = app.getFile(fileName);
if( file.exists() && !Files.readFile( file ).startsWith(CreatedBy) ){
throw confError(
s"File '$fileName' already exists and was not created by SlickDDLPlugin."
+s" Please delete file or remove datasource '$db' from configuration for"
+s" $configKey"
)
}
evolutionScript(db).foreach { script =>
Files.createDirectory(app.getFile(dir));
Files.writeFileIfChanged(file, script);
}
}
}
}
}
/** Load AutoDDL object from user Play project using reflection
*/
def fetchAutoDDLobject = {
val mirror = scala.reflect.runtime.universe.runtimeMirror( app.classloader )
val instance =
try{
mirror.reflectModule(
mirror.staticModule("play.api.db.slick.AutoDDL")
).instance
} catch { case e: reflect.internal.MissingRequirementError =>
throw confError(
"Could not find singleton object AutoDDL in package play.api.db.slick."
+" In order to use Play-Slick's AutoDDL feature you need to define"
+" it extending play.api.db.slick.AutoDDLInterface ."
)
}
try{
instance.asInstanceOf[AutoDDLInterface]
} catch { case e: ClassCastException =>
throw confError(
"Found singleton object AutoDDL in package play.api.db.slick,"
+" but it does not extend play.api.db.slick.AutoDDLInterface, which"
+" is required."
)
}
}
/** generates DDL for given datasource */
def evolutionScript(db:String): Option[String] = {
val tables =
try{
fetchAutoDDLobject.tables
} catch {
case e:java.lang.LinkageError =>
throw confError(
s"LinkageError when trying to load play.api.db.slick.AutoDDL.tables via reflection: '$e'."
+" In the definition of play.api.db.slick.AutoDDL.tables you probably referred to a val,"
+" which was not yet initialized. Please refer to defs, singleton objects or alternatively"
+" instantiate the table objects right in the definiton, e.g. like "+"""
def tables = Map(
"default" -> Seq(new MyTableA,new MyTableB)
)"""
, Some(e)
)
}
val ddls = tables
.get(db)
.map(_.map(_.ddl))
.getOrElse{
throw confError(s"play.api.db.slick.AutoDDL.tables did not contain datasource '$db'")
}
val delimiter = ";" //TODO: figure this out by asking the db or have a configuration setting?
if (ddls.nonEmpty) {
val ddl = ddls.reduceLeft(_ ++ _)
Some(CreatedBy+ "\n" +
"# To stop Slick DDL generation, remove this comment and start using Evolutions\n" +
"\n" +
"# --- !Ups\n\n" +
ddl.createStatements.mkString("", s"$delimiter\n", s"$delimiter\n") +
"\n" +
"# --- !Downs\n\n" +
ddl.dropStatements.mkString("", s"$delimiter\n", s"$delimiter\n") +
"\n")
} else None
}
}