1
- # scala-quillcodegen
1
+ # scala-db-codegen
2
2
3
- This is an sbt-plugin and mill-plugin that uses the [ quill-codegen-jdbc] ( https://zio.dev/zio-quill/code-generation/ ) to generate case classes and query schemas from a database schema.
3
+ A sbt-plugin and mill-plugin to generate boilerplate code from a database schema. Tested with SQLite and Postgresql. Should work for all databases supported by jdbc.
4
+
5
+ > DbSchema + Template => generate scala code
6
+
7
+ The plugin can be configured to crawl a database schema to extract all tables/columns/enums. It matches the occurring jdbc/sql types to scala types.
8
+ You can provide a [ scalate] ( https://scalate.github.io/scalate/ ) template to generate scala code out of this information like this:
9
+ ``` scala
10
+ <%@ val schema : dbcodegen.DataSchema %>
11
+
12
+ package kicks .db .$ {schema .name }
13
+
14
+ # for (table <- schema.tables)
15
+ case class $ {table.scalaName}(
16
+ # for (column <- table.columns)
17
+ $ {column.scalaName}: $ {column.scalaType},
18
+ # end
19
+ )
20
+ # end
21
+ ```
4
22
5
- Works with scala 2 and 3.
6
23
7
24
## Usage
8
25
9
26
### sbt
10
27
11
28
In ` project/plugins.sbt ` :
12
29
``` sbt
13
- addSbtPlugin(" com.github.cornerman" % " sbt-quillcodegen " % " 0.2.0" )
30
+ addSbtPlugin(" com.github.cornerman" % " sbt-db-codegen " % " 0.2.0" )
14
31
```
15
32
16
33
In ` build.sbt ` :
17
34
``` sbt
18
35
lazy val db = project
19
- .enablePlugins(quillcodegen .plugin.CodegenPlugin )
36
+ .enablePlugins(dbcodegen .plugin.DbCodegenPlugin )
20
37
.settings(
21
- // The package prefix for the generated code
22
- quillcodegenPackagePrefix := " com.example.db" ,
23
38
// The jdbc URL for the database
24
- quillcodegenJdbcUrl := " jdbc:..." ,
39
+ dbcodegenJdbcUrl := " jdbc:..." ,
40
+ // The template file for the code generator
41
+ dbcodegenTemplateFiles := Seq (file(" schema.scala.ssp" ))
25
42
26
43
// Optional database username
27
- // quillcodegenUsername := None,
44
+ // dbcodegenUsername := None,
28
45
// Optional database password
29
- // quillcodegenPassword := None,
30
- // The naming parser to use, default is SnakeCaseNames
31
- // quillcodegenNaming := SnakeCaseNames,
32
- // Whether to generate a nested extensions trait, default is false
33
- // quillcodegenNestedTrait := false,
34
- // Whether to generate query schemas, default is true
35
- // quillcodegenGenerateQuerySchema := true,
36
- // Specify which tables to process, default is all
37
- // quillcodegenTableFilter := (_ => true),
38
- // Strategy for unrecognized types
39
- // quillcodegenUnrecognizedType := SkipColumn,
40
- // Map jdbc types to java/scala types
41
- // quillcodegenTypeMapping := ((_, classTag) => classTag),
42
- // Which numeric type preference for numeric types
43
- // quillcodegenNumericType := UseDefaults,
44
- // Timeout for the generate task
45
- // quillcodegenTimeout := Duration.Inf,
46
+ // dbcodegenPassword := None,
47
+ // Map sql types to java/scala types
48
+ // dbcodegenTypeMapping := (sqlType: SQLType, scalaType: Option[String]) => scalaType,
49
+ // Filter which schema and table should be processed
50
+ // dbcodegenSchemaTableFilter := (schema: String, table: String) => true
46
51
// Setup task to be executed before the code generation runs against the database
47
- // quillcodegenSetupTask := {},
52
+ // dbcodegenSetupTask := {},
48
53
)
49
54
```
50
55
51
56
#### Setup database before codegen
52
57
53
- An example for using the ` quillcodegenSetupTask ` to setup an sqlite database with a ` schema.sql ` file before the code generation runs:
58
+ An example for using the ` dbcodegenSetupTask ` to setup an sqlite database with a ` schema.sql ` file before the code generation runs:
54
59
``` sbt
55
- quillcodegenSetupTask := Def .taskDyn {
56
- IO .delete(file(quillcodegenJdbcUrl .value.stripPrefix(" jdbc:sqlite:" )))
60
+ dbcodegenSetupTask := Def .taskDyn {
61
+ IO .delete(file(dbcodegenJdbcUrl .value.stripPrefix(" jdbc:sqlite:" )))
57
62
executeSqlFile(file(" ./schema.sql" ))
58
63
}
59
64
```
@@ -64,17 +69,148 @@ The functions `executeSql` and `executeSqlFile` are provided for these kind of u
64
69
### mill
65
70
66
71
In ` build.sc ` :
67
- ```
72
+ ``` scala
68
73
import mill ._ , scalalib ._
69
- import $ivy.`com.github.cornerman::mill-quillcodegen :0.2.0`, quillcodegen .plugin.QuillCodegenModule
74
+ import $ivy .`com.github.cornerman::mill-db-codegen :0.2.0` , dbcodegen .plugin .DbCodegenModule
70
75
71
- object backend extends ScalaModule with QuillCodegenModule {
72
- def quillcodegenJdbcUrl = "com.example.db",
73
- def quillcodegenPackagePrefix = "dbtypes"
74
- def quillcodegenSetupTask = T.task {
75
- val dbpath = quillcodegenJdbcUrl.stripPrefix("jdbc:sqlite:")
76
+ object backend extends ScalaModule with DbCodegenModule {
77
+ // The jdbc URL for the database
78
+ def dbcodegenJdbcUrl = " jdbc:sqlite:..."
79
+ // The template file for the code generator
80
+ def dbcodegenTemplateFiles = Seq (PathRef (os.pwd / " schema.scala.ssp" ))
81
+ // Setup task to be executed before the code generation runs against the database
82
+ def dbcodegenSetupTask = T .task {
83
+ val dbpath = dbcodegenJdbcUrl.stripPrefix(" jdbc:sqlite:" )
76
84
os.remove(os.pwd / dbpath)
77
85
executeSqlFile(PathRef (os.pwd / " schema.sql" ))
78
86
}
87
+ // Optional database username
88
+ // def dbcodegenUsername = None
89
+ // Optional database password
90
+ // def dbcodegenPassword = None
91
+ // Map sql types to java/scala types
92
+ // def dbcodegenTypeMapping = (sqlType: SQLType, scalaType: Option[String]) => scalaType
93
+ // Filter which schema and table should be processed
94
+ // def dbcodegenSchemaTableFilter = (schema: String, table: String) => true
79
95
}
80
96
```
97
+
98
+ ## Template Examples
99
+
100
+ Template can be configured by setting ` dbcodegenTemplateFiles ` .
101
+
102
+ We are using [ scalate] ( https://scalate.github.io/scalate/ ) for templates, so you can use anything that is supported there (e.g. ` mustache ` or ` ssp ` ) - the converter will be picked according to the file extension of the provided template file. Check the [ scalate user guide] ( https://scalate.github.io/scalate/documentation/user-guide.html ) for more details.
103
+
104
+ The template is called on each database schema, and is passed an instance of [ ` dbcodegen.DataSchema ` ] ( codegen/src/main/scala/dbcodegen/DataSchema.scala ) (variable name ` schema ` ) which contains all the extracted information.
105
+ You can see the declaration in the first line of each ` ssp ` template.
106
+
107
+ ### Simple
108
+
109
+ case-classes.scala.ssp:
110
+ ``` scala
111
+ <%@ val schema : dbcodegen.DataSchema %>
112
+
113
+ package kicks .db .$ {schema .name }
114
+
115
+ # for (enum <- schema.enums)
116
+ type $ {enum .scalaName} = $ {enum .values.map(v => " \" " + v.name + " \" " ).mkString(" | " )}
117
+ # end
118
+
119
+ # for (table <- schema.tables)
120
+
121
+ case class $ {table.scalaName}(
122
+ # for (column <- table.columns)
123
+ $ {column.scalaName}: $ {column.scalaType},
124
+ # end
125
+ )
126
+
127
+ # end
128
+ ```
129
+
130
+ #### with scala 3 enums
131
+
132
+ ``` scala
133
+ # for (enum <- schema.enums)
134
+
135
+ enum $ {enum .scalaName}(val sqlValue : String ) {
136
+ # for (enumValue <- enum .values)
137
+ case $ {enumValue.scalaName} extends $ {enum .scalaName}(" ${enumValue.name}" )
138
+ # end
139
+ }
140
+ object $ {enum .scalaName} {
141
+ def bySqlValue (searchValue : String ): Option [$ {enum .scalaName}] = values.find(_.sqlValue == searchValue)
142
+ }
143
+
144
+ # end
145
+ ```
146
+
147
+ ### Library: quill
148
+
149
+ quill-case-classes.scala.ssp:
150
+ ``` scala
151
+ <%@ val schema : dbcodegen.DataSchema %>
152
+
153
+ package kicks .db .$ {schema .scalaName }
154
+
155
+ import io .getquill .*
156
+
157
+ # for (enum <- schema.enums)
158
+ type $ {enum .scalaName} = $ {enum .values.map(v => " \" " + v.name + " \" " ).mkString(" | " )}
159
+ # end
160
+
161
+ # for (table <- schema.tables)
162
+
163
+ case class $ {table.scalaName}(
164
+ # for (column <- table.columns)
165
+ $ {column.scalaName}: $ {column.scalaType},
166
+ # end
167
+ )
168
+ object $ {table.scalaName} {
169
+ inline def query = querySchema[Person ](
170
+ " ${table.name}" ,
171
+ # for (column <- table.columns)
172
+ _.$ {column.scalaName} -> " ${column.name}" ,
173
+ # end
174
+ )
175
+ }
176
+
177
+ # end
178
+ ```
179
+
180
+ ### Library: magnum
181
+
182
+ magnum-case-classes.scala.ssp:
183
+ ``` scala
184
+ <%@ val schema : dbcodegen.DataSchema %>
185
+
186
+ package kicks .db .$ {schema .scalaName }
187
+
188
+ import com .augustnagro .magnum .*
189
+
190
+ # for (enum <- schema.enums)
191
+ type $ {enum .scalaName} = $ {enum .values.map(v => " \" " + v.name + " \" " ).mkString(" | " )}
192
+ # end
193
+
194
+ # for (table <- schema.tables)
195
+
196
+ @ Table (SqliteDbType )
197
+ case class $ {table.scalaName}(
198
+ # for (column <- table.columns)
199
+ @ SqlName (" ${column.name}" ) $ {column.scalaName}: $ {column.scalaType},
200
+ # end
201
+ ) derives DbCodec
202
+ object $ {table.scalaName} {
203
+ # { val primaryKeyColumns = table.columns.filter(_.isPartOfPrimaryKey)}#
204
+ type Id = $ {if (primaryKeyColumns.isEmpty) " Null" else primaryKeyColumns.map(_.scalaType).mkString(" (" , " , " , " )" )}
205
+
206
+ case class Creator (
207
+ # for (column <- table.columns if ! column.isAutoGenerated)
208
+ $ {column.scalaName}: $ {column.scalaType},
209
+ # end
210
+ )
211
+ }
212
+
213
+ val $ {table.scalaName}Repo = Repo [$ {table.scalaName}.Creator , $ {table.scalaName}, $ {table.scalaName}.Id ]
214
+
215
+ # end
216
+ ```
0 commit comments