1
1
[ // ] : # ( GENERATED SOURCES, DO NOT EDIT DIRECTLY )
2
2
3
3
4
- This tutorials is a tour of how to use ScalaSql, from the most basic concepts
4
+ This tutorial is a tour of how to use ScalaSql, from the most basic concepts
5
5
to writing some realistic queries. If you are browsing this on Github, you
6
6
can open the ` Outline ` pane on the right to browse the section headers to
7
7
see what we will cover and find anything specific of interest to you.
@@ -71,8 +71,14 @@ supported databases, to see what kind of set up is necessary for each one
71
71
### Modeling Your Schema
72
72
73
73
Next, you need to define your data model classes. In ScalaSql, your data model
74
- is defined using ` case class ` es with each field wrapped in the wrapper type
75
- parameter ` T[_] ` . This allows us to re-use the same case class to represent
74
+ is defined using ` case class ` es with each field representing a column in an database table.
75
+
76
+ There are two flavors to consider: ` Table ` (available for Scala 2.13+), and ` SimpleTable ` (Scala 3.7+).
77
+
78
+ ** Using ` Table ` **
79
+
80
+ Declare your case class with a type parameter ` T[_] ` , which is used to wrap the type of each
81
+ field. This allows us to re-use the same case class to represent
76
82
both database values (when ` T ` is ` scalasql.Expr ` ) as well as Scala values
77
83
(when ` T ` is ` scalasql.Sc ` ).
78
84
@@ -120,6 +126,59 @@ case class CountryLanguage[T[_]](
120
126
object CountryLanguage extends Table [CountryLanguage ]()
121
127
```
122
128
129
+ ** Using ` SimpleTable ` **
130
+ > Note: only available in the ` com.lihaoyi::scalasql-namedtuples ` library, which supports Scala 3.7.0+
131
+
132
+ Declare your case class as usual. Inside of queries, the class will be represented by a ` Record ` with the same fields, but wrapped in ` scalasql.Expr ` .
133
+
134
+ Here, we define three classes ` Country ` ` City ` and ` CountryLanguage ` , modeling
135
+ the database tables we saw above.
136
+
137
+ Also included is the necessary import statement to include the ` SimpleTable ` definition.
138
+
139
+ ``` scala
140
+ import scalasql .simple .{* , given }
141
+
142
+ case class Country (
143
+ code : String ,
144
+ name : String ,
145
+ continent : String ,
146
+ region : String ,
147
+ surfaceArea : Int ,
148
+ indepYear : Option [Int ],
149
+ population : Long ,
150
+ lifeExpectancy : Option [Double ],
151
+ gnp : Option [scala.math.BigDecimal ],
152
+ gnpOld : Option [scala.math.BigDecimal ],
153
+ localName : String ,
154
+ governmentForm : String ,
155
+ headOfState : Option [String ],
156
+ capital : Option [Int ],
157
+ code2 : String
158
+ )
159
+
160
+ object Country extends SimpleTable [Country ]
161
+
162
+ case class City (
163
+ id : Int ,
164
+ name : String ,
165
+ countryCode : String ,
166
+ district : String ,
167
+ population : Long
168
+ )
169
+
170
+ object City extends SimpleTable [City ]
171
+
172
+ case class CountryLanguage (
173
+ countryCode : String ,
174
+ language : String ,
175
+ isOfficial : Boolean ,
176
+ percentage : Double
177
+ )
178
+
179
+ object CountryLanguage extends SimpleTable [CountryLanguage ]
180
+ ```
181
+
123
182
### Creating Your Database Client
124
183
Lastly, we need to initialize our ` scalasql.DbClient ` . This requires
125
184
passing in a ` java.sql.Connection ` , a ` scalasql.Config ` object, and the SQL dialect
@@ -201,11 +260,16 @@ db.run(query).take(3) ==> Seq(
201
260
)
202
261
203
262
```
204
- Notice that ` db.run ` returns instances of type ` City[Sc] ` . ` Sc ` is ` scalasql.Sc ` ,
263
+ Notice that ` db.run ` returns instances of type ` City[Sc] ` (or ` City ` if using ` SimpleTable ` ).
264
+
265
+ ` Sc ` is ` scalasql.Sc ` ,
205
266
short for the "Scala" type, representing a ` City ` object containing normal Scala
206
267
values. The ` [Sc] ` type parameter must be provided explicitly whenever creating,
207
268
type-annotating, or otherwise working with these ` City ` values.
208
269
270
+ > In this tutorial, unless otherwise specified, we will assume usage of the ` Table ` encoding.
271
+ > If you are using ` SimpleTable ` , the same code will work, but drop ` [Sc] ` type arguments.
272
+
209
273
In this example, we do ` .take(3) ` after running the query to show only the first
210
274
3 table entries for brevity, but by that point the ` City.select ` query had already
211
275
fetched the entire database table into memory. This can be a problem with non-trivial
@@ -235,8 +299,12 @@ db.run(query) ==> City[Sc](3208, "Singapore", "SGP", district = "", population =
235
299
```
236
300
Note that we use ` === ` rather than ` == ` for the equality comparison. The
237
301
function literal passed to ` .filter ` is given a ` City[Expr] ` as its parameter,
238
- representing a ` City ` that is part of the database query, in contrast to the
239
- ` City[Sc] ` s that ` db.run ` returns , and so ` _.name ` is of type ` Expr[String] `
302
+ (or ` Record[City, Expr] ` with the ` SimpleTable ` encoding) representing a ` City `
303
+ that is part of the database query, in contrast to the
304
+ ` City[Sc] ` s that ` db.run ` returns.
305
+
306
+ Within a query therefore ` _.name ` is a field selection on the function parameter,
307
+ resulting in ` Expr[String] ` ,
240
308
rather than just ` String ` or ` Sc[String] ` . You can use your IDE's
241
309
auto-complete to see what operations are available on ` Expr[String] ` : typically
242
310
they will represent SQL string functions rather than Scala string functions and
@@ -309,7 +377,8 @@ db.run(query).take(2) ==> Seq(
309
377
)
310
378
311
379
```
312
- Again, all the operations within the query work on ` Expr ` s: ` c ` is a ` City[Expr] ` ,
380
+ Again, all the operations within the query work on ` Expr ` s:
381
+ ` c ` is a ` City[Expr] ` (or ` Record[City, Expr] ` for ` SimpleTable ` ),
313
382
` c.population ` is an ` Expr[Int] ` , ` c.countryCode ` is an ` Expr[String] ` , and
314
383
` === ` and ` > ` and ` && ` on ` Expr ` s all return ` Expr[Boolean] ` s that represent
315
384
a SQL expression that can be sent to the Database as part of your query.
@@ -427,8 +496,61 @@ db.run(query) ==>
427
496
" SINGAPORE" ,
428
497
4 // population in millions
429
498
)
499
+
430
500
```
431
501
502
+ ** Mapping with named tuples**
503
+ > Note: only available in the ` com.lihaoyi::scalasql-namedtuples ` library, which supports Scala 3.7.0+
504
+
505
+ You can also use named tuples to map the results of a query.
506
+ ``` scala
507
+ import scalasql .namedtuples .NamedTupleQueryable .given
508
+
509
+ val query = Country .select.map(c =>
510
+ (name = c.name, continent = c.continent)
511
+ )
512
+
513
+ db.run(query).take(5 ) ==> Seq (
514
+ (name = " Afghanistan" , continent = " Asia" ),
515
+ (name = " Netherlands" , continent = " Europe" ),
516
+ (name = " Netherlands Antilles" , continent = " North America" ),
517
+ (name = " Albania" , continent = " Europe" ),
518
+ (name = " Algeria" , continent = " Africa" )
519
+ )
520
+ ```
521
+
522
+ ** Updating ` Record ` fields**
523
+ > Note: only relevant when using the ` SimpleTable ` encoding.
524
+
525
+ When using ` SimpleTable ` , within the ` .map ` query ` c ` is of type
526
+ ` Record[Country, Expr] ` . Records are converted back to their associated case class
527
+ (e.g. ` Country ` ) with ` db.run ` .
528
+
529
+ If you want to apply updates to any of the fields before returning, the ` Record ` class
530
+ provides an ` updates ` method. This lets you provide an arbitrary sequence of updates to
531
+ apply in-order to the record. You can either provide a value with ` := ` ,
532
+ or provide a function that transforms the old value. For example:
533
+
534
+ ``` scala
535
+ val query = Country .select.map(c =>
536
+ c.updates(
537
+ _.population := 0L ,
538
+ _.name(old => Expr (" 🌐 " ) + old)
539
+ )
540
+ )
541
+
542
+ db.run(query).take(5 ).match {
543
+ case Seq (
544
+ Country (name = " 🌐 Afghanistan" , population = 0L ),
545
+ Country (name = " 🌐 Netherlands" , population = 0L ),
546
+ Country (name = " 🌐 Netherlands Antilles" , population = 0L ),
547
+ Country (name = " 🌐 Albania" , population = 0L ),
548
+ Country (name = " 🌐 Algeria" , population = 0L )
549
+ ) =>
550
+ } ==> ()
551
+ ```
552
+
553
+
432
554
### Aggregates
433
555
434
556
You can perform simple aggregates like ` .sum ` as below, where we
@@ -1361,6 +1483,23 @@ db.run(
1361
1483
1362
1484
db.run(City.select.filter(_.id === 313373).single) ==>
1363
1485
City[Sc](CityId(313373), "test", "XYZ", "district", 1000000)
1486
+
1487
+
1488
+ ```
1489
+ You can also use `TypeMapper#bimap` for the common case where you want the
1490
+ new `TypeMapper` to behave the same as an existing `TypeMapper`, just with
1491
+ conversion functions to convert back and forth between the old type and new type:
1492
+
1493
+ ```scala
1494
+ case class CityId2(value: Int)
1495
+
1496
+ object CityId2 {
1497
+ implicit def tm: TypeMapper[CityId2] = TypeMapper[Int].bimap[CityId2](
1498
+ city => city.value,
1499
+ int => CityId2(int)
1500
+ )
1501
+ }
1502
+
1364
1503
```
1365
1504
1366
1505
```scala
@@ -1387,8 +1526,7 @@ db.run(
1387
1526
1388
1527
db.run(City2.select.filter(_.id === 31337).single) ==>
1389
1528
City2[Sc](CityId2(31337), "test", "XYZ", "district", 1000000)
1390
-
1391
- st("customTableColumnNames") {
1529
+ ```
1392
1530
1393
1531
## Customizing Table and Column Names
1394
1532
0 commit comments