diff --git a/docs/tutorial.md b/docs/tutorial.md index eb85ae7d..34cd47f1 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -1334,7 +1334,7 @@ object CityId { ```scala -case class City2[T[_]]( +case class City[T[_]]( id: T[CityId], name: T[String], countryCode: T[String], @@ -1342,12 +1342,38 @@ case class City2[T[_]]( population: T[Long] ) +object City extends Table[City]() { + override def tableName: String = "city" +} +db.run( + City.insert.columns( + _.id := CityId(313373), + _.name := "test", + _.countryCode := "XYZ", + _.district := "district", + _.population := 1000000 + ) +) + +db.run(City.select.filter(_.id === 313373).single) ==> + City[Sc](CityId(313373), "test", "XYZ", "district", 1000000) +``` + +```scala +case class City2[T[_]]( + id: T[CityId2], + name: T[String], + countryCode: T[String], + district: T[String], + population: T[Long] +) + object City2 extends Table[City2]() { override def tableName: String = "city" } db.run( City2.insert.columns( - _.id := CityId(31337), + _.id := CityId2(31337), _.name := "test", _.countryCode := "XYZ", _.district := "district", @@ -1356,8 +1382,9 @@ db.run( ) db.run(City2.select.filter(_.id === 31337).single) ==> - City2[Sc](CityId(31337), "test", "XYZ", "district", 1000000) -``` + City2[Sc](CityId2(31337), "test", "XYZ", "district", 1000000) + +st("customTableColumnNames") { ## Customizing Table and Column Names diff --git a/scalasql/core/src/TypeMapper.scala b/scalasql/core/src/TypeMapper.scala index 0978d1eb..d855570f 100644 --- a/scalasql/core/src/TypeMapper.scala +++ b/scalasql/core/src/TypeMapper.scala @@ -29,7 +29,7 @@ import java.util.UUID * Defaults are provided for most common Scala primitives, but you can also provide * your own by defining an `implicit val foo: TypeMapper[T]` */ -trait TypeMapper[T] { +trait TypeMapper[T] { outer => /** * The JDBC type of this type. Used for `setNull` which needs to know the @@ -51,9 +51,21 @@ trait TypeMapper[T] { * How to insert a value of type [[T]] into a `PreparedStatement` */ def put(r: PreparedStatement, idx: Int, v: T): Unit + + /** + * Create a new `TypeMapper[V]` based on this `TypeMapper[T]` given the + * two conversion functions `f: V => T`, `g: T => V` + */ + def bimap[V](f: V => T, g: T => V): TypeMapper[V] = new TypeMapper[V] { + def jdbcType: JDBCType = outer.jdbcType + override def castTypeString: String = outer.castTypeString + def get(r: ResultSet, idx: Int): V = g(outer.get(r, idx)) + def put(r: PreparedStatement, idx: Int, v: V): Unit = outer.put(r, idx, f(v)) + } } object TypeMapper { + def apply[T](implicit t: TypeMapper[T]): TypeMapper[T] = t /** * These definitions are workarounds for a bug in the Scala 3 compiler diff --git a/scalasql/src/package.scala b/scalasql/src/package.scala index 85fc9948..7a04ee2e 100644 --- a/scalasql/src/package.scala +++ b/scalasql/src/package.scala @@ -36,6 +36,7 @@ package object scalasql { type Expr[T] = core.Expr[T] type TypeMapper[T] = core.TypeMapper[T] + val TypeMapper = core.TypeMapper val Config = core.Config type Config = core.Config diff --git a/scalasql/test/src/WorldSqlTests.scala b/scalasql/test/src/WorldSqlTests.scala index 2aac6611..0bc472db 100644 --- a/scalasql/test/src/WorldSqlTests.scala +++ b/scalasql/test/src/WorldSqlTests.scala @@ -1420,7 +1420,7 @@ object WorldSqlTests extends TestSuite { SqlStr.Interp.TypeInterp[CityId](CityId(1337)) // +DOCS - case class City2[T[_]]( + case class City[T[_]]( id: T[CityId], name: T[String], countryCode: T[String], @@ -1428,12 +1428,54 @@ object WorldSqlTests extends TestSuite { population: T[Long] ) + object City extends Table[City]() { + override def tableName: String = "city" + } + db.run( + City.insert.columns( + _.id := CityId(313373), + _.name := "test", + _.countryCode := "XYZ", + _.district := "district", + _.population := 1000000 + ) + ) + + db.run(City.select.filter(_.id === 313373).single) ==> + City[Sc](CityId(313373), "test", "XYZ", "district", 1000000) + // -DOCS + + // You can also use `TypeMapper#bimap` for the common case where you want the + // new `TypeMapper` to behave the same as an existing `TypeMapper`, just with + // conversion functions to convert back and forth between the old type and new type: + + case class CityId2(value: Int) + + object CityId2 { + implicit def tm: TypeMapper[CityId2] = TypeMapper[Int].bimap[CityId2]( + city => city.value, + int => CityId2(int) + ) + } + + // -DOCS + // Note sure why this is required, probably a Scalac bug + SqlStr.Interp.TypeInterp[CityId2](CityId2(1337)) + // +DOCS + case class City2[T[_]]( + id: T[CityId2], + name: T[String], + countryCode: T[String], + district: T[String], + population: T[Long] + ) + object City2 extends Table[City2]() { override def tableName: String = "city" } db.run( City2.insert.columns( - _.id := CityId(31337), + _.id := CityId2(31337), _.name := "test", _.countryCode := "XYZ", _.district := "district", @@ -1442,8 +1484,7 @@ object WorldSqlTests extends TestSuite { ) db.run(City2.select.filter(_.id === 31337).single) ==> - City2[Sc](CityId(31337), "test", "XYZ", "district", 1000000) - // -DOCS + City2[Sc](CityId2(31337), "test", "XYZ", "district", 1000000) } test("customTableColumnNames") { // +DOCS