Migration Notes (Upgrading to 0.3.x from 0.2.x)
パッケージ
パッケージ名の変更
0.2.x | 0.3.x |
---|---|
ldbc-core | ldbc-schema |
新たなパッケージ
新たに2種類のパッケージが追加されました。
Module / Platform | JVM | Scala Native | Scala.js |
---|---|---|---|
ldbc-connector |
✅ | ✅ | ✅ |
jdbc-connector |
✅ | ❌ | ❌ |
全てのパッケージ
Module / Platform | JVM | Scala Native | Scala.js |
---|---|---|---|
ldbc-sql |
✅ | ✅ | ✅ |
ldbc-connector |
✅ | ✅ | ✅ |
jdbc-connector |
✅ | ❌ | ❌ |
ldbc-dsl |
✅ | ✅ | ✅ |
ldbc-query-builder |
✅ | ✅ | ✅ |
ldbc-schema |
✅ | ✅ | ✅ |
ldbc-schemaSpy |
✅ | ❌ | ❌ |
ldbc-codegen |
✅ | ✅ | ✅ |
ldbc-hikari |
✅ | ❌ | ❌ |
ldbc-plugin |
✅ | ❌ | ❌ |
機能変更
コネクタ切り替え機能
Scala MySQL コネクタに、JDBC と ldbc の接続切り替えのサポートが追加されました。
この変更により、開発者はプロジェクトの要件に応じて JDBC または ldbc ライブラリを使用したデータベース接続を柔軟に選択できるようになりました。これにより、開発者は異なるライブラリの機能を利用できるようになり、接続の設定や操作の柔軟性が向上します。
変更方法
まず、共通の依存関係を設定する。
libraryDependencies += "io.github.takapi327" %% "ldbc-dsl" % "0.3.0-beta8"
クロスプラットフォームプロジェクトでは(JVM、JS、ネイティブ)
libraryDependencies += "io.github.takapi327" %%% "ldbc-dsl" % "0.3.0-beta8"
使用される依存パッケージは、データベース接続が Java API を使用するコネクタを介して行われるか、または ldbc によって提供されるコネクタを介して行われるかによって異なります。
jdbcコネクタの使用
libraryDependencies += "io.github.takapi327" %% "jdbc-connector" % "0.3.0-beta8"
ldbcコネクタの使用
libraryDependencies += "io.github.takapi327" %% "ldbc-connector" % "0.3.0-beta8"
クロスプラットフォームプロジェクトでは(JVM、JS、ネイティブ)
libraryDependencies += "io.github.takapi327" %%% "ldbc-connector" % "0.3.0-beta8"
使用方法
jdbcコネクタの使用
val ds = new com.mysql.cj.jdbc.MysqlDataSource()
ds.setServerName("127.0.0.1")
ds.setPortNumber(13306)
ds.setDatabaseName("world")
ds.setUser("ldbc")
ds.setPassword("password")
val datasource = jdbc.connector.MysqlDataSource[IO](ds)
val connection: Resource[IO, Connection[IO]] =
Resource.make(datasource.getConnection)(_.close())
ldbcコネクタの使用
val connection: Resource[IO, Connection[IO]] =
ldbc.connector.Connection[IO](
host = "127.0.0.1",
port = 3306,
user = "ldbc",
password = Some("password"),
database = Some("ldbc"),
ssl = SSL.Trusted
)
データベースへの接続処理は、それぞれの方法で確立されたコネクションを使って行うことができる。
val result: IO[(List[Int], Option[Int], Int)] = connection.use { conn =>
(for
result1 <- sql"SELECT 1".query[Int].to[List]
result2 <- sql"SELECT 2".query[Int].to[Option]
result3 <- sql"SELECT 3".query[Int].unsafe
yield (result1, result2, result3)).readOnly(conn)
}
破壊的変更
プレーン・クエリ構築の拡張
プレーン・クエリを用いたデータベース接続メソッドによる検索対象の型の決定は、検索対象の型とそのフォーマット(リストまたはオプション)を一括して指定していた。
今回の修正ではこれを変更し、取得する型とその形式の指定を分離することで内部ロジックを共通化した。これにより、プレーン・クエリの構文はよりdoobieに近くなり、doobieのユーザは混乱することなく使用できるはずである。
before
sql"SELECT id, name, age FROM user".toList[(Long, String, Int)].readOnly(connection)
sql"SELECT id, name, age FROM user WHERE id = ${1L}".headOption[User].readOnly(connection)
after
sql"SELECT id, name, age FROM user".query[(Long, String, Int)].to[List].readOnly(connection)
sql"SELECT id, name, age FROM user WHERE id = ${1L}".query[User].to[Option].readOnly(connection)
AUTO INCREMENT値取得メソッド命名変更
更新 API で AUTO INCREMENT 列によって生成された値を変換する API updateReturningAutoGeneratedKey
の名前が returning
に変更されました。
これはMySQLの特徴で、MySQLはデータ挿入時にAUTO INCREMENTで生成された値を返しますが、他のRDBは動作が異なり、AUTO INCREMENTで生成された値以外の値を返すことがあります。 API 名は、将来の拡張を考慮して、限定的な API 名をより拡張しやすくするために早い段階で変更されました。
before
sql"INSERT INTO `table`(`id`, `c1`) VALUES ($None, ${ "column 1" })".updateReturningAutoGeneratedKey[Long]
after
sql"INSERT INTO `table`(`id`, `c1`) VALUES ($None, ${ "column 1" })".returning[Long]
クエリビルダーの構築方法
以前まではクエリビルダーはテーブルスキーマを構築しなければ使用することができませんでした。
今回の更新で、より簡易的にクエリビルダーを使用できるように変更を行いました。
before
まずモデルに対応したテーブルスキーマを作成し、
case class User(
id: Long,
name: String,
age: Option[Int],
)
val userTable = Table[User]("user")( // CREATE TABLE `user` (
column("id", BIGINT, AUTO_INCREMENT, PRIMARY_KEY), // `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
column("name", VARCHAR(255)), // `name` VARCHAR(255) NOT NULL,
column("age", INT.UNSIGNED.DEFAULT(None)), // `age` INT unsigned DEFAULT NULL
)
次にテーブルスキーマを使用してTableQuery
の構築を行います。
val tableQuery = TableQuery[IO, User](userTable)
最後にクエリ構築を行っていました。
val result: IO[List[User]] = connection.use { conn =>
tableQuery.selectAll.toList[User].readOnly(conn)
// "SELECT `id`, `name`, `age` FROM user"
}
after
今回の変更によって、モデルを構築し
import ldbc.query.builder.Table
case class User(
id: Long,
name: String,
age: Option[Int],
) derives Table
次にTable
を初期化を行います。
import ldbc.query.builder.Table
val userTable = Table[User]
最後にクエリ構築を行うことで利用可能となります。
val result: IO[List[User]] = connection.use { conn =>
userTable.selectAll.query.to[List].readOnly(conn)
// "SELECT `id`, `name`, `age` FROM user"
}
カスタムデータ型のサポート
ユーザー定義のデータ型を使用する際は、ResultSetReader
とParameter
を使用してカスタムデータ型をサポートしていました。
今回の更新で、ResultSetReader
とParameter
を使用してカスタムデータ型をサポートする方法が変更されました。
Encoder
クエリ文字列に動的に埋め込むために、Parameter
からEncoder
に変更。
これにより、ユーザはEffect Typeを受け取るための冗長な処理を記述する必要がなくなり、よりシンプルな実装とカスタムデータ型のパラメータとしての使用が可能になります。
enum Status(val code: Int, val name: String):
case Active extends Status(1, "Active")
case InActive extends Status(2, "InActive")
Before
given Parameter[Status] with
override def bind[F[_]](
statement: PreparedStatement[F],
index: Int,
status: Status
): F[Unit] = statement.setInt(index, status.code)
After
given Encoder[Status] with
override def encode(status: Status): Int = status.done
Encoder
のエンコード処理では、PreparedStatement
で扱えるScala型しか返すことができません。
現在、以下のタイプがサポートされている。
Scala Type | Methods called in PreparedStatement |
---|---|
Boolean |
setBoolean |
Byte |
setByte |
Short |
setShort |
Int |
setInt |
Long |
setLong |
Float |
setFloat |
Double |
setDouble |
BigDecimal |
setBigDecimal |
String |
setString |
Array[Byte] |
setBytes |
java.time.LocalDate |
setDate |
java.time.LocalTime |
setTime |
java.time.LocalDateTime |
setTimestamp |
None |
setNull |
Decoder
ResultSet
からデータを取得する処理をResultSetReader
からDecoder
に変更。
これにより、ユーザーは取得したレコードをネストした階層データに変換できる。
case class City(id: Int, name: String, countryCode: String)
case class Country(code: String, name: String)
case class CityWithCountry(city: City, country: Country)
sql"SELECT city.Id, city.Name, city.CountryCode, country.Code, country.Name FROM city JOIN country ON city.CountryCode = country.Code".query[CityWithCountry]
Using Query Builder
case class City(id: Int, name: String, countryCode: String) derives Table
case class Country(code: String, name: String) derives Table
val city = Table[City]
val country = Table[Country]
city.join(country).join((city, country) => city.countryCode === country.code)
.select((city, country) => (city.name, country.name))
.query // (String, String)
.to[Option]
city.join(country).join((city, country) => city.countryCode === country.code)
.selectAll
.query // (City, Country)
.to[Option]