0
# Type Mappings
1
2
Column type mappings between Scala types and database types, including custom type mappings and implicit conversions.
3
4
## Capabilities
5
6
### Column Types
7
8
Built-in mappings between Scala types and database column types.
9
10
```scala { .api }
11
/**
12
* Base trait for typed database types
13
*/
14
trait TypedType[T] {
15
/** SQL type name for this type */
16
def sqlTypeName(sym: Option[FieldSymbol]): String
17
18
/** JDBC type code */
19
def sqlType: Int
20
21
/** Whether this type has a length parameter */
22
def hasLiteralLength: Boolean
23
}
24
25
/**
26
* Column type with conversion between database and Scala representation
27
*/
28
trait ColumnType[T] extends TypedType[T] {
29
/** Convert from JDBC ResultSet to Scala type */
30
def getValue(r: ResultSet, idx: Int): T
31
32
/** Convert from Scala type to PreparedStatement parameter */
33
def setValue(v: T, p: PreparedStatement, idx: Int): Unit
34
35
/** Whether this type can represent NULL values */
36
def nullable: Boolean
37
38
/** Create an Option version of this type */
39
def optionType: ColumnType[Option[T]]
40
}
41
42
/**
43
* Base column type without user-defined conversions
44
*/
45
trait BaseColumnType[T] extends ColumnType[T]
46
```
47
48
### Built-in Type Mappings
49
50
Standard mappings for common Scala types to database column types.
51
52
```scala { .api }
53
/**
54
* Implicit column type instances for basic types
55
*/
56
object ColumnTypes {
57
implicit val booleanColumnType: BaseColumnType[Boolean]
58
implicit val byteColumnType: BaseColumnType[Byte]
59
implicit val shortColumnType: BaseColumnType[Short]
60
implicit val intColumnType: BaseColumnType[Int]
61
implicit val longColumnType: BaseColumnType[Long]
62
implicit val floatColumnType: BaseColumnType[Float]
63
implicit val doubleColumnType: BaseColumnType[Double]
64
implicit val stringColumnType: BaseColumnType[String]
65
implicit val bigDecimalColumnType: BaseColumnType[BigDecimal]
66
67
// Date/Time types
68
implicit val dateColumnType: BaseColumnType[Date]
69
implicit val timeColumnType: BaseColumnType[Time]
70
implicit val timestampColumnType: BaseColumnType[Timestamp]
71
implicit val localDateColumnType: BaseColumnType[LocalDate]
72
implicit val localTimeColumnType: BaseColumnType[LocalTime]
73
implicit val localDateTimeColumnType: BaseColumnType[LocalDateTime]
74
implicit val instantColumnType: BaseColumnType[Instant]
75
76
// Binary types
77
implicit val byteArrayColumnType: BaseColumnType[Array[Byte]]
78
implicit val blobColumnType: BaseColumnType[Blob]
79
implicit val clobColumnType: BaseColumnType[Clob]
80
81
// UUID type
82
implicit val uuidColumnType: BaseColumnType[UUID]
83
}
84
85
/**
86
* Optional type mapping
87
*/
88
implicit def optionColumnType[T](implicit base: ColumnType[T]): ColumnType[Option[T]]
89
```
90
91
**Usage Examples:**
92
93
```scala
94
// Basic type usage in table definitions
95
class Products(tag: Tag) extends Table[Product](tag, "products") {
96
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
97
def name = column[String]("name")
98
def price = column[BigDecimal]("price")
99
def isActive = column[Boolean]("is_active", O.Default(true))
100
def createdAt = column[Timestamp]("created_at")
101
def description = column[Option[String]]("description") // Optional column
102
def metadata = column[Array[Byte]]("metadata")
103
def uuid = column[UUID]("uuid")
104
105
def * = (id, name, price, isActive, createdAt, description, metadata, uuid).mapTo[Product]
106
}
107
108
// Using Java 8 time types
109
class Events(tag: Tag) extends Table[Event](tag, "events") {
110
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
111
def eventDate = column[LocalDate]("event_date")
112
def eventTime = column[LocalTime]("event_time")
113
def eventDateTime = column[LocalDateTime]("event_datetime")
114
def timestamp = column[Instant]("timestamp")
115
116
def * = (id, eventDate, eventTime, eventDateTime, timestamp).mapTo[Event]
117
}
118
```
119
120
### Custom Type Mappings
121
122
Create custom mappings between Scala types and database column types.
123
124
```scala { .api }
125
/**
126
* Create a custom column type mapping
127
*/
128
object MappedColumnType {
129
/**
130
* Create a mapped column type with bidirectional conversion
131
* @param tmap Function to convert from Scala type T to database type U
132
* @param tcomap Function to convert from database type U to Scala type T
133
*/
134
def apply[T, U](tmap: T => U, tcomap: U => T)(implicit tm: ColumnType[U]): ColumnType[T]
135
136
/**
137
* Create a mapped column type for base types
138
*/
139
def base[T, U](tmap: T => U, tcomap: U => T)(implicit tm: BaseColumnType[U]): BaseColumnType[T]
140
}
141
```
142
143
**Usage Examples:**
144
145
```scala
146
// Custom enum mapping
147
sealed trait CoffeeSize
148
case object Small extends CoffeeSize
149
case object Medium extends CoffeeSize
150
case object Large extends CoffeeSize
151
152
object CoffeeSize {
153
implicit val coffeesSizeMapper = MappedColumnType.base[CoffeeSize, String](
154
{
155
case Small => "S"
156
case Medium => "M"
157
case Large => "L"
158
},
159
{
160
case "S" => Small
161
case "M" => Medium
162
case "L" => Large
163
}
164
)
165
}
166
167
// Use custom enum in table
168
class Orders(tag: Tag) extends Table[Order](tag, "orders") {
169
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
170
def coffeeId = column[Int]("coffee_id")
171
def size = column[CoffeeSize]("size") // Uses custom mapping
172
def quantity = column[Int]("quantity")
173
174
def * = (id, coffeeId, size, quantity).mapTo[Order]
175
}
176
177
// JSON mapping example
178
case class ProductMetadata(tags: List[String], features: Map[String, String])
179
180
implicit val jsonMapper = MappedColumnType.base[ProductMetadata, String](
181
metadata => Json.toJson(metadata).toString(),
182
jsonString => Json.parse(jsonString).as[ProductMetadata]
183
)
184
185
class ProductsWithJson(tag: Tag) extends Table[(Int, String, ProductMetadata)](tag, "products") {
186
def id = column[Int]("id", O.PrimaryKey)
187
def name = column[String]("name")
188
def metadata = column[ProductMetadata]("metadata") // JSON column
189
190
def * = (id, name, metadata)
191
}
192
193
// Custom value class mapping
194
case class UserId(value: Long) extends AnyVal
195
case class ProductId(value: Long) extends AnyVal
196
197
implicit val userIdMapper = MappedColumnType.base[UserId, Long](_.value, UserId.apply)
198
implicit val productIdMapper = MappedColumnType.base[ProductId, Long](_.value, ProductId.apply)
199
200
class OrdersWithValueClasses(tag: Tag) extends Table[Order](tag, "orders") {
201
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
202
def userId = column[UserId]("user_id") // Uses value class mapping
203
def productId = column[ProductId]("product_id") // Uses value class mapping
204
def quantity = column[Int]("quantity")
205
206
def * = (id, userId, productId, quantity).mapTo[Order]
207
}
208
```
209
210
### Database-Specific Types
211
212
Handle database-specific column types and extensions.
213
214
```scala { .api }
215
/**
216
* PostgreSQL-specific types (example)
217
*/
218
object PostgresTypes {
219
/** PostgreSQL array type */
220
implicit def arrayColumnType[T](implicit base: ColumnType[T]): ColumnType[List[T]]
221
222
/** PostgreSQL JSON/JSONB type */
223
implicit val jsonColumnType: ColumnType[JsValue]
224
implicit val jsonbColumnType: ColumnType[JsValue]
225
226
/** PostgreSQL UUID type */
227
implicit val uuidColumnType: ColumnType[UUID]
228
229
/** PostgreSQL range types */
230
implicit val intRangeColumnType: ColumnType[Range[Int]]
231
implicit val timestampRangeColumnType: ColumnType[Range[Timestamp]]
232
}
233
234
/**
235
* MySQL-specific types (example)
236
*/
237
object MySQLTypes {
238
/** MySQL YEAR type */
239
implicit val yearColumnType: ColumnType[Year]
240
241
/** MySQL SET type */
242
implicit def setColumnType[T](implicit base: ColumnType[T]): ColumnType[Set[T]]
243
244
/** MySQL spatial types */
245
implicit val pointColumnType: ColumnType[Point]
246
implicit val geometryColumnType: ColumnType[Geometry]
247
}
248
```
249
250
**Usage Examples:**
251
252
```scala
253
// PostgreSQL array columns
254
import slick.jdbc.PostgresProfile.api._
255
import PostgresTypes._
256
257
class TaggedItems(tag: Tag) extends Table[(Int, String, List[String])](tag, "tagged_items") {
258
def id = column[Int]("id", O.PrimaryKey)
259
def name = column[String]("name")
260
def tags = column[List[String]]("tags") // PostgreSQL array
261
262
def * = (id, name, tags)
263
}
264
265
// PostgreSQL JSON columns
266
case class UserPreferences(theme: String, language: String, notifications: Boolean)
267
268
implicit val preferencesMapper = MappedColumnType.base[UserPreferences, JsValue](
269
prefs => Json.toJson(prefs),
270
json => json.as[UserPreferences]
271
)
272
273
class UsersWithPreferences(tag: Tag) extends Table[(Int, String, UserPreferences)](tag, "users") {
274
def id = column[Int]("id", O.PrimaryKey)
275
def name = column[String]("name")
276
def preferences = column[UserPreferences]("preferences") // JSONB column
277
278
def * = (id, name, preferences)
279
}
280
281
// MySQL spatial types (hypothetical)
282
import slick.jdbc.MySQLProfile.api._
283
import MySQLTypes._
284
285
class Locations(tag: Tag) extends Table[(Int, String, Point)](tag, "locations") {
286
def id = column[Int]("id", O.PrimaryKey)
287
def name = column[String]("name")
288
def coordinates = column[Point]("coordinates") // MySQL POINT type
289
290
def * = (id, name, coordinates)
291
}
292
```
293
294
### Type Conversions
295
296
Automatic conversions and lifting between related types.
297
298
```scala { .api }
299
/**
300
* Implicit conversions for lifted embedding
301
*/
302
object TypeConversions {
303
/** Convert literal values to column representations */
304
implicit def constColumnType[T](implicit tm: TypedType[T]): ColumnType[T]
305
306
/** Lift literal values to column expressions */
307
implicit def literalColumn[T : TypedType](v: T): LiteralColumn[T]
308
309
/** Convert between compatible numeric types */
310
implicit def numericConversion[T, U](c: Rep[T])(implicit tm: TypedType[U], conv: T => U): Rep[U]
311
312
/** Option lifting */
313
implicit def optionLift[T](c: Rep[T]): Rep[Option[T]]
314
}
315
316
/**
317
* Type-safe null handling
318
*/
319
trait NullHandling {
320
/** Check if optional column is defined */
321
def isDefined[T](col: Rep[Option[T]]): Rep[Boolean]
322
323
/** Check if optional column is empty */
324
def isEmpty[T](col: Rep[Option[T]]): Rep[Boolean]
325
326
/** Provide default value for null columns */
327
def ifNull[T](col: Rep[Option[T]], default: Rep[T]): Rep[T]
328
329
/** Get value from option or throw exception */
330
def get[T](col: Rep[Option[T]]): Rep[T]
331
}
332
```
333
334
**Usage Examples:**
335
336
```scala
337
// Automatic type lifting
338
val coffees = TableQuery[Coffees]
339
340
// Literal values are automatically lifted to column expressions
341
val expensiveFilter = coffees.filter(_.price > 3.0) // 3.0 becomes LiteralColumn[Double]
342
val nameFilter = coffees.filter(_.name === "Latte") // "Latte" becomes LiteralColumn[String]
343
344
// Option handling
345
val activeUsers = users.filter(_.deletedAt.isEmpty)
346
val usersWithRoles = users.map(u => (u.name, u.role.ifNull("guest")))
347
348
// Type conversions in expressions
349
val pricesInCents = coffees.map(c => (c.name, c.price * 100)) // Double to Int conversion
350
val formattedPrices = coffees.map(c => c.name ++ ": $" ++ c.price.asColumnOf[String])
351
352
// Numeric conversions
353
class Statistics(tag: Tag) extends Table[(Int, Double, Float)](tag, "statistics") {
354
def id = column[Int]("id", O.PrimaryKey)
355
def average = column[Double]("average")
356
def percentage = column[Float]("percentage")
357
358
def * = (id, average, percentage)
359
360
// Automatic conversion between numeric types in queries
361
def highPercentage = this.filter(_.percentage > 0.8) // Double literal converted to Float
362
}
363
364
// Custom implicit conversions
365
case class Price(amount: BigDecimal, currency: String = "USD")
366
367
implicit val priceColumnType = MappedColumnType.base[Price, BigDecimal](
368
_.amount,
369
Price(_)
370
)
371
372
implicit def priceOrdering: Ordering[Rep[Price]] =
373
Ordering.by((_: Rep[Price]).asColumnOf[BigDecimal])
374
375
class ProductsWithPrices(tag: Tag) extends Table[(Int, String, Price)](tag, "products") {
376
def id = column[Int]("id", O.PrimaryKey)
377
def name = column[String]("name")
378
def price = column[Price]("price")
379
380
def * = (id, name, price)
381
382
// Can now sort by Price using custom ordering
383
def sortedByPrice = this.sortBy(_.price)
384
}
385
```
386
387
### Validation and Constraints
388
389
Add validation and constraints to custom type mappings.
390
391
```scala { .api }
392
/**
393
* Validated column type with runtime validation
394
*/
395
case class ValidatedColumnType[T](
396
base: ColumnType[T],
397
validate: T => Either[String, T]
398
) extends ColumnType[T] {
399
400
override def getValue(r: ResultSet, idx: Int): T = {
401
val value = base.getValue(r, idx)
402
validate(value) match {
403
case Right(validated) => validated
404
case Left(error) => throw new SQLException(s"Validation failed: $error")
405
}
406
}
407
408
override def setValue(v: T, p: PreparedStatement, idx: Int): Unit = {
409
validate(v) match {
410
case Right(validated) => base.setValue(validated, p, idx)
411
case Left(error) => throw new SQLException(s"Validation failed: $error")
412
}
413
}
414
}
415
```
416
417
**Usage Examples:**
418
419
```scala
420
// Email validation
421
case class Email(value: String) extends AnyVal
422
423
object Email {
424
private val emailRegex = """^[a-zA-Z0-9\._%+-]+@[a-zA-Z0-9\.-]+\.[a-zA-Z]{2,}$""".r
425
426
def apply(value: String): Either[String, Email] = {
427
if (emailRegex.matches(value)) Right(new Email(value))
428
else Left(s"Invalid email format: $value")
429
}
430
431
implicit val emailColumnType = ValidatedColumnType(
432
MappedColumnType.base[Email, String](_.value, new Email(_)),
433
(email: Email) => if (emailRegex.matches(email.value)) Right(email) else Left("Invalid email")
434
)
435
}
436
437
// Positive number validation
438
case class PositiveInt(value: Int) extends AnyVal
439
440
object PositiveInt {
441
implicit val positiveIntColumnType = ValidatedColumnType(
442
MappedColumnType.base[PositiveInt, Int](_.value, PositiveInt(_)),
443
(pi: PositiveInt) => if (pi.value > 0) Right(pi) else Left("Must be positive")
444
)
445
}
446
447
class ValidatedTable(tag: Tag) extends Table[(Int, Email, PositiveInt)](tag, "validated") {
448
def id = column[Int]("id", O.PrimaryKey)
449
def email = column[Email]("email") // Validated email format
450
def quantity = column[PositiveInt]("quantity") // Validated positive number
451
452
def * = (id, email, quantity)
453
}
454
455
// Range validation
456
case class Percentage(value: Double) extends AnyVal
457
458
object Percentage {
459
implicit val percentageColumnType = ValidatedColumnType(
460
MappedColumnType.base[Percentage, Double](_.value, Percentage(_)),
461
(p: Percentage) => {
462
if (p.value >= 0.0 && p.value <= 100.0) Right(p)
463
else Left(s"Percentage must be between 0 and 100, got ${p.value}")
464
}
465
)
466
}
467
```
468
469
## Types
470
471
```scala { .api }
472
trait TypedType[T] {
473
def sqlTypeName(sym: Option[FieldSymbol]): String
474
def sqlType: Int
475
def hasLiteralLength: Boolean
476
}
477
478
trait ColumnType[T] extends TypedType[T] {
479
def getValue(r: ResultSet, idx: Int): T
480
def setValue(v: T, p: PreparedStatement, idx: Int): Unit
481
def nullable: Boolean
482
def optionType: ColumnType[Option[T]]
483
}
484
485
trait BaseColumnType[T] extends ColumnType[T]
486
487
object MappedColumnType {
488
def apply[T, U](tmap: T => U, tcomap: U => T)(implicit tm: ColumnType[U]): ColumnType[T]
489
def base[T, U](tmap: T => U, tcomap: U => T)(implicit tm: BaseColumnType[U]): BaseColumnType[T]
490
}
491
492
// Built-in type mappings
493
implicit val intColumnType: BaseColumnType[Int]
494
implicit val stringColumnType: BaseColumnType[String]
495
implicit val doubleColumnType: BaseColumnType[Double]
496
implicit val booleanColumnType: BaseColumnType[Boolean]
497
implicit val bigDecimalColumnType: BaseColumnType[BigDecimal]
498
implicit val timestampColumnType: BaseColumnType[Timestamp]
499
implicit val dateColumnType: BaseColumnType[Date]
500
implicit val uuidColumnType: BaseColumnType[UUID]
501
502
// Option types
503
implicit def optionColumnType[T](implicit base: ColumnType[T]): ColumnType[Option[T]]
504
505
// Database-specific extensions
506
trait DatabaseSpecificTypes {
507
implicit def arrayColumnType[T](implicit base: ColumnType[T]): ColumnType[List[T]]
508
implicit val jsonColumnType: ColumnType[JsValue]
509
}
510
```