or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

database-io.mddatabase-profiles.mdindex.mdplain-sql.mdqueries.mdtable-definitions.mdtype-mappings.md

type-mappings.mddocs/

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

```