diff --git a/docs/src/main/mdoc/ja/migration-notes.md b/docs/src/main/mdoc/ja/migration-notes.md index 2576aaf95..daac830ff 100644 --- a/docs/src/main/mdoc/ja/migration-notes.md +++ b/docs/src/main/mdoc/ja/migration-notes.md @@ -1,9 +1,9 @@ {% - laika.title = Migration Notes + laika.title = マイグレーションノート laika.metadata.language = ja %} -# Migration Notes (Upgrading to 0.3.x from 0.2.x) +# マイグレーションノート (0.2.xから0.3.xへの移行) ## パッケージ @@ -309,22 +309,15 @@ enum Status(val code: Int, val name: String): case InActive extends Status(2, "InActive") ``` -**Before** - -```scala 3 -given Parameter[Status] with - override def bind[F[_]]( - statement: PreparedStatement[F], - index: Int, - status: Status - ): F[Unit] = statement.setInt(index, status.code) -``` +```diff +-given Parameter[Status] with +- override def bind[F[_]]( +- statement: PreparedStatement[F], +- index: Int, +- status: Status +- ): F[Unit] = statement.setInt(index, status.code) -**After** - -```scala 3 -given Encoder[Status] with - override def encode(status: Status): Int = status.done ++given Encoder[Status] = Encoder[Int].contramap(_.code) ``` `Encoder`のエンコード処理では、`PreparedStatement`で扱えるScala型しか返すことができません。 @@ -348,11 +341,77 @@ given Encoder[Status] with | `java.time.LocalDateTime` | `setTimestamp` | | `None` | `setNull` | +また、Encoderは複数の型を合成して新しい型を作成することができます。 + +```scala 3 +val encoder: Encoder[(Int, String)] = Encoder[Int] *: Encoder[String] +``` + +合成した型は任意のクラスに変換することもできます。 + +```scala 3 +case class Status(code: Int, name: String) +given Encoder[Status] = (Encoder[Int] *: Encoder[String]).to[Status] +``` + #### Decoder `ResultSet`からデータを取得する処理を`ResultSetReader`から`Decoder`に変更。 -これにより、ユーザーは取得したレコードをネストした階層データに変換できる。 +```diff +-given ResultSetReader[IO, Status] = +- ResultSetReader.mapping[IO, Int, Status](code => Status.fromCode(code)) ++given Decoder[Status] = Decoder[Int].map(code => Status.fromCode(code)) +``` + +Decoderも複数の型を合成して新しい型を作成することができます。 + +```scala 3 +val decoder: Decoder[(Int, String)] = Decoder[Int] *: Decoder[String] +``` + +合成した型は任意のクラスに変換することもできます。 + +```scala 3 +case class Status(code: Int, name: String) +given Decoder[Status] = (Decoder[Int] *: Decoder[String]).to[Status] +``` + +### Codecの導入 + +`Codec`は、`Encoder`と`Decoder`を組み合わせたもので、`Codec`を使用することで、`Encoder`と`Decoder`を組み合わせることができます。 + +```scala 3 +enum Status(val code: Int, val name: String): + case Active extends Status(1, "Active") + case InActive extends Status(2, "InActive") + +given Codec[Status] = Codec[Int].imap(Status.fromCode)(_.code) +``` + +Codecも複数の型を合成して新しい型を作成することができます。 + +```scala 3 +val codec: Codec[(Int, String)] = Codec[Int] *: Codec[String] +``` + +合成した型は任意のクラスに変換することもできます。 + +```scala 3 +case class Status(code: Int, name: String) +given Codec[Status] = (Codec[Int] *: Codec[String]).to[Status] +``` + +Codecは、`Encoder`と`Decoder`を組み合わせたものであるため、それぞれの型への変換処理を行うことができます。 + +```scala 3 +val encoder: Encoder[Status] = Codec[Status].asEncoder +val decoder: Decoder[Status] = Codec[Status].asDecoder +``` + +今回の変更により、ユーザーは`Codec`を使用して、`Encoder`と`Decoder`を組み合わせることができるようになりました。 + +これにより、ユーザーは取得したレコードをネストした階層データに変換できます。 ```scala case class City(id: Int, name: String, countryCode: String) @@ -362,24 +421,35 @@ 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** +Codecを始め`Encoder`と`Decoder`は暗黙的に解決されるため、ユーザーはこれらの型を明示的に指定する必要はありません。 -```scala 3 -case class City(id: Int, name: String, countryCode: String) derives Table -case class Country(code: String, name: String) derives Table +しかし、モデル内に多くのプロパティがある場合、暗黙的な検索は失敗する可能性があります。 + +```shell +[error] |Implicit search problem too large. +[error] |an implicit search was terminated with failure after trying 100000 expressions. +[error] |The root candidate for the search was: +[error] | +[error] | given instance given_Decoder_P in object Decoder for ldbc.dsl.codec.Decoder[City]} +``` + +このような場合は、コンパイルオプションの検索制限を上げると問題が解決することがあります。 -val city = Table[City] -val country = Table[Country] +```scala +scalacOptions += "-Ximplicit-search-limit:100000" +``` -city.join(country).on((city, country) => city.countryCode === country.code) - .select((city, country) => (city.name, country.name)) - .query // (String, String) - .to[Option] +しかし、オプションでの制限拡張はコンパイル時間の増幅につながる可能性があります。その場合は、以下のように手動で任意の型を構築することで解決することもできます。 -city.join(country).on((city, country) => city.countryCode === country.code) - .selectAll - .query // (City, Country) - .to[Option] +```scala 3 +given Decoder[City] = (Decoder[Int] *: Decoder[String] *: Decoder[Int] *: ....).to[City] +given Encoder[City] = (Encoder[Int] *: Encoder[String] *: Encoder[Int] *: ....).to[City] +``` + +もしくは、`Codec`を使用して`Encoder`と`Decoder`を組み合わせることで解決することもできます。 + +```scala 3 +given Codec[City] = (Codec[Int] *: Codec[String] *: Codec[Int] *: ....).to[City] ``` ### 列の絞り込み方法の変更 diff --git a/docs/src/main/mdoc/ja/tutorial/Custom-Data-Type.md b/docs/src/main/mdoc/ja/tutorial/Custom-Data-Type.md index 7bd18bb23..e48d810c7 100644 --- a/docs/src/main/mdoc/ja/tutorial/Custom-Data-Type.md +++ b/docs/src/main/mdoc/ja/tutorial/Custom-Data-Type.md @@ -32,37 +32,135 @@ enum Status(val done: Boolean, val name: String): これによりstatementにカスタム型をバインドすることができるようになります。 ```scala 3 -given Encoder[Status] with - override def encode(status: Status): Boolean = status.done +given Encoder[Status] = Encoder[Boolean].contramap(_.done) ``` カスタム型は他のパラメーターと同じようにstatementにバインドすることができます。 ```scala -val program1: Executor[IO, Int] = +val program1: DBIO[Int] = sql"INSERT INTO user (name, email, status) VALUES (${ "user 1" }, ${ "user@example.com" }, ${ Status.Active })".update ``` これでstatementにカスタム型をバインドすることができるようになりました。 +また、Encoderは複数の型を合成して新しい型を作成することができます。 + +```scala 3 +val encoder: Encoder[(Int, String)] = Encoder[Int] *: Encoder[String] +``` + +合成した型は任意のクラスに変換することもできます。 + +```scala 3 +case class Status(code: Int, name: String) +given Encoder[Status] = (Encoder[Int] *: Encoder[String]).to[Status] +``` + ## Decoder ldbcではパラメーターの他に実行結果から独自の型を取得するための`Decoder`も提供しています。 `Decoder`を実装することでstatementの実行結果から独自の型を取得することができます。 -以下のコード例では、`Decoder.Elem`を使用して単一のデータ型を取得する方法を示しています。 +以下のコード例では、`Decoder`を使用して単一のデータ型を取得する方法を示しています。 ```scala 3 -given Decoder.Elem[Status] = Decoder.Elem.mapping[Boolean, Status] { +given Decoder[Status] = Decoder[Boolean].map { case true => Status.Active case false => Status.InActive } ``` ```scala 3 -val program2: Executor[IO, (String, String, Status)] = +val program2: DBIO[(String, String, Status)] = sql"SELECT name, email, status FROM user WHERE id = 1".query[(String, String, Status)].unsafe ``` これでstatementの実行結果からカスタム型を取得することができるようになりました。 + +Decoderも複数の型を合成して新しい型を作成することができます。 + +```scala 3 +val decoder: Decoder[(Int, String)] = Decoder[Int] *: Decoder[String] +``` + +合成した型は任意のクラスに変換することもできます。 + +```scala 3 +case class Status(code: Int, name: String) +given Decoder[Status] = (Decoder[Int] *: Decoder[String]).to[Status] +``` + +## Codec + +`Encoder`と`Decoder`を組み合わせた`Codec`を使用することでstatementに受け渡す値とstatementの実行結果から独自の型を取得することができます。 + +以下のコード例では、`Codec`を使用して先ほどの`Encoder`と`Decoder`を組み合わせた方法を示しています。 + +```scala 3 +given Codec[Status] = Codec[Boolean].imap(_.done)(Status(_)) +``` + +Codecも複数の型を合成して新しい型を作成することができます。 + +```scala 3 +val codec: Codec[(Int, String)] = Codec[Int] *: Codec[String] +``` + +合成した型は任意のクラスに変換することもできます。 + +```scala 3 +case class Status(code: Int, name: String) +given Codec[Status] = (Codec[Int] *: Codec[String]).to[Status] +``` + +Codecは、`Encoder`と`Decoder`を組み合わせたものであるため、それぞれの型への変換処理を行うことができます。 + +```scala 3 +val encoder: Encoder[Status] = Codec[Status].asEncoder +val decoder: Decoder[Status] = Codec[Status].asDecoder +``` + +`Codec`, `Encoder`, `Decoder`はそれぞれ合成することができるため、複数の型を組み合わせて新しい型を作成することができます。 + +これにより、ユーザーは取得したレコードをネストした階層データに変換できます。 + +```scala +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] +``` + +Codecを始め`Encoder`と`Decoder`は暗黙的に解決されるため、ユーザーはこれらの型を明示的に指定する必要はありません。 + +しかし、モデル内に多くのプロパティがある場合、暗黙的な検索は失敗する可能性があります。 + +```shell +[error] |Implicit search problem too large. +[error] |an implicit search was terminated with failure after trying 100000 expressions. +[error] |The root candidate for the search was: +[error] | +[error] | given instance given_Decoder_P in object Decoder for ldbc.dsl.codec.Decoder[City]} +``` + +このような場合は、コンパイルオプションの検索制限を上げると問題が解決することがあります。 + +```scala +scalacOptions += "-Ximplicit-search-limit:100000" +``` + +しかし、オプションでの制限拡張はコンパイル時間の増幅につながる可能性があります。その場合は、以下のように手動で任意の型を構築することで解決することもできます。 + +```scala 3 +given Decoder[City] = (Decoder[Int] *: Decoder[String] *: Decoder[Int] *: ....).to[City] +given Encoder[City] = (Encoder[Int] *: Encoder[String] *: Encoder[Int] *: ....).to[City] +``` + +もしくは、`Codec`を使用して`Encoder`と`Decoder`を組み合わせることで解決することもできます。 + +```scala 3 +given Codec[City] = (Codec[Int] *: Codec[String] *: Codec[Int] *: ....).to[City] +``` diff --git a/docs/src/main/mdoc/ja/tutorial/Database-Operations.md b/docs/src/main/mdoc/ja/tutorial/Database-Operations.md index 17c04c0f0..c94ae3432 100644 --- a/docs/src/main/mdoc/ja/tutorial/Database-Operations.md +++ b/docs/src/main/mdoc/ja/tutorial/Database-Operations.md @@ -35,10 +35,10 @@ val write = sql"INSERT INTO `table`(`c1`, `c2`) VALUES ('column 1', 'column 2')" `transaction`メソッドを使用することで複数のデータベース接続処理を1つのトランザクションにまとめることができます。 -ldbcは`Executor[F, A]`という形式でデータベースへの接続処理を組むことになる。 Executorはモナドなので、for内包を使って2つの小さなプログラムを1つの大きなプログラムにすることができる。 +ldbcは`DBIO[A]`という形式でデータベースへの接続処理を組むことになる。 DBIOはモナドなので、for内包を使って2つの小さなプログラムを1つの大きなプログラムにすることができる。 -```scala -val program: Executor[IO, (List[Int], Option[Int], Int)] = +```scala 3 +val program: DBIO[(List[Int], Option[Int], Int)] = for result1 <- sql"SELECT 1".query[Int].to[List] result2 <- sql"SELECT 2".query[Int].to[Option] @@ -46,7 +46,7 @@ val program: Executor[IO, (List[Int], Option[Int], Int)] = yield (result1, result2, result3) ``` -1つのプログラムとなった`Executor`を`transaction`メソッドで1つのトランザクションでまとめて処理を行うことができます。 +1つのプログラムとなった`DBIO`を`transaction`メソッドで1つのトランザクションでまとめて処理を行うことができます。 ```scala val transaction = program.transaction(connection) diff --git a/docs/src/main/mdoc/ja/tutorial/Error-Handling.md b/docs/src/main/mdoc/ja/tutorial/Error-Handling.md index 1c2ea1b42..f129b965a 100644 --- a/docs/src/main/mdoc/ja/tutorial/Error-Handling.md +++ b/docs/src/main/mdoc/ja/tutorial/Error-Handling.md @@ -9,7 +9,7 @@ ## 例外について -ある操作が成功するかどうかは、ネットワークの健全性、テーブルの現在の内容、ロックの状態など、予測できない要因に依存します。そのため、`EitherT[Executor, Throwable, A]`のような論理和ですべてを計算するか、明示的に捕捉されるまで例外の伝播を許可するかを決めなければならない。つまり、ldbcのアクション(ターゲット・モナドに変換される)が実行されると、例外が発生する可能性がある。 +ある操作が成功するかどうかは、ネットワークの健全性、テーブルの現在の内容、ロックの状態など、予測できない要因に依存します。そのため、`EitherT[DBIO, Throwable, A]`のような論理和ですべてを計算するか、明示的に捕捉されるまで例外の伝播を許可するかを決めなければならない。つまり、ldbcのアクション(ターゲット・モナドに変換される)が実行されると、例外が発生する可能性がある。 発生しやすい例外は主に3種類ある @@ -19,7 +19,7 @@ ## モナド・エラーと派生コンバイネーター -すべてのldbcモナドは、`MonadError[?[_], Throwable]`を拡張したAsyncインスタンスを提供する。つまり、Executorなどは以下のようなプリミティブな操作を持つことになる +すべてのldbcモナドは、`MonadError[?[_], Throwable]`を拡張したAsyncインスタンスを提供する。つまり、DBIOなどは以下のようなプリミティブな操作を持つことになる - raiseError: 例外を発生させる (Throwableを`M[A]`に変換する) - handleErrorWith: 例外を処理する (`M[A]`を`M[B]`に変換する) @@ -28,10 +28,10 @@ つまり、どんなldbcプログラムでも`attempt`を加えるだけで例外を捕捉することができるのだ。 ```scala -val program = Executor.pure[IO, Int](1) +val program = DBIO.pure[IO, Int](1) program.attempt -// Executor[IO, Either[Throwable, Int]] +// DBIO[IO, Either[Throwable, Int]] ``` `attempt`と`raiseError`コンビネータから、Catsのドキュメントで説明されているように、他の多くの操作を派生させることができます。 diff --git a/docs/src/main/mdoc/ja/tutorial/Schema-Code-Generation.md b/docs/src/main/mdoc/ja/tutorial/Schema-Code-Generation.md index d48bd69e1..9f2590bf1 100644 --- a/docs/src/main/mdoc/ja/tutorial/Schema-Code-Generation.md +++ b/docs/src/main/mdoc/ja/tutorial/Schema-Code-Generation.md @@ -132,7 +132,7 @@ database: `columns`には型を変更したいカラム名と変更したいScalaの型を文字列で記載を行います。`columns`には複数の値を設定できますが、nameに記載されたカラム名が対象のテーブルに含まれいてなければなりません。 また、変換を行うScalaの型はカラムのData型がサポートしている型である必要があります。もしサポート対象外の型を指定したい場合は、`object`に対して暗黙の型変換を行う設定を持ったtraitやabstract classなどを渡してあげる必要があります。 -Data型がサポートしている型に関しては[こちら](/ja/tutorial/Schema.md#データ型)を、サポート対象外の型を設定する方法は[こちら](/ja/tutorial/Schema.md#カスタム-データ型)を参照してください。 +Data型がサポートしている型に関しては[こちら](/ja/tutorial/Custom-Data-Type.md)を、サポート対象外の型を設定する方法は[こちら](/ja/tutorial/Custom-Data-Type.md)を参照してください。 Int型をユーザー独自の型であるCountryCodeに変換する場合は、以下のような`CustomMapping`traitを実装します。 @@ -143,7 +143,10 @@ object Japan extends CountryCode: override val code: Int = 1 trait CustomMapping: // 任意の名前 - given Conversion[INT[Int], CountryCode] = DataType.mappingp[INT[Int], CountryCode] + given Codec[CountryCode] = Codec[Int].imap { + case 1 => Japan + case _ => throw new Exception("Not found") + }(_.code) ``` カスタマイズを行うためのymlファイルに実装を行なった`CustomMapping`traitを設定し、対象のカラムの型をCountryCodeに変換してあげます。 diff --git a/docs/src/main/mdoc/ja/tutorial/Schema.md b/docs/src/main/mdoc/ja/tutorial/Schema.md index 58e746a09..b97684e40 100644 --- a/docs/src/main/mdoc/ja/tutorial/Schema.md +++ b/docs/src/main/mdoc/ja/tutorial/Schema.md @@ -108,389 +108,3 @@ Null可能な列は`Option[T]`で表現され、Tはサポートされるプリ | `BIGINT` | `-9223372036854775808 ~ 9223372036854775807` | `0 ~ 18446744073709551615` | `Long
BigInt` | `-9223372036854775808~9223372036854775807
...` | ユーザー定義の独自型やサポートされていない型を扱う場合は、カスタムデータ型を参照してください。 - -## 属性 - -カラムにはさまざまな属性を割り当てることができます。 - -- `AUTO_INCREMENT` - DDL文を作成し、SchemaSPYを文書化する際に、列を自動インクリメント・キーとしてマークする。 - MySQLでは、データ挿入時にAutoIncでないカラムを返すことはできません。そのため、必要に応じて、ldbcは戻りカラムがAutoIncとして適切にマークされているかどうかを確認します。 -- `PRIMARY_KEY` - DDL文やSchemaSPYドキュメントを作成する際に、列を主キーとしてマークする。 -- `UNIQUE_KEY` - DDL文やSchemaSPYドキュメントを作成する際に、列を一意キーとしてマークする。 -- `COMMENT` - DDL文やSchemaSPY文書を作成する際に、列にコメントを設定する。 - -## キーの設定 - -MySQLではテーブルに対してUniqueキーやIndexキー、外部キーなどの様々なキーを設定することができます。ldbcで構築したテーブル定義でこれらのキーを設定する方法を見ていきましょう。 - -### PRIMARY KEY - -主キー(primary key)とはMySQLにおいてデータを一意に識別するための項目のことです。カラムにプライマリーキー制約を設定すると、カラムには他のデータの値を重複することのない値しか格納することができなくなります。また NULL も格納することができません。その結果、プライマリーキー制約が設定されたカラムの値を検索することで、テーブルの中でただ一つのデータを特定することができます。 - -ldbcではこのプライマリーキー制約を2つの方法で設定することができます。 - -1. columnメソッドの属性として設定する -2. tableのkeySetメソッドで設定する - -**columnメソッドの属性として設定する** - -columnメソッドの属性として設定する方法は非常に簡単で、columnメソッドの第3引数以降に`PRIMARY_KEY`を渡すだけです。これによって以下の場合 `id`カラムを主キーとして設定することができます。 - -```scala 3 -column("id", BIGINT, AUTO_INCREMENT, PRIMARY_KEY) -``` - -**tableのkeySetメソッドで設定する** - -ldbcのテーブル定義には `keySet`というメソッドが生えており、ここで`PRIMARY_KEY`に主キーとして設定したいカラムを渡すことで主キーとして設定することができます。 - -```scala 3 -val table = Table[User]("user")( - column("id", INT, AUTO_INCREMENT), - column("name", VARCHAR(50)), - column("email", VARCHAR(100)) -) - .keySet(table => PRIMARY_KEY(table.id)) - -// CREATE TABLE `user` ( -// ..., -// PRIMARY KEY (`id`) -// ) -``` - -`PRIMARY_KEY`メソッドにはカラム意外にも以下のパラメーターを設定することができます。 - -- `Index Type` ldbc.schema.Index.Type.BTREE or ldbc.schema.Index.Type.HASH -- `Index Option` ldbc.schema.Index.IndexOption - -#### 複合キー (primary key) - -1つのカラムだけではなく、複数のカラムを主キーとして組み合わせ主キーとして設定することもできます。`PRIMARY_KEY`に主キーとして設定したいカラムを複数渡すだけで複合主キーとして設定することができます。 - -```scala 3 -val table = Table[User]("user")( - column("id", INT, AUTO_INCREMENT), - column("name", VARCHAR(50)), - column("email", VARCHAR(100)) -) - .keySet(table => PRIMARY_KEY(table.id, table.name)) - -// CREATE TABLE `user` ( -// ..., -// PRIMARY KEY (`id`, `name`) -// ) -``` - -複合キーは`keySet`メソッドでの`PRIMARY_KEY`でしか設定することはできません。仮に以下のようにcolumnメソッドの属性として複数設定を行うと複合キーとしてではなく、それぞれを主キーとして設定されてしまいます。 - -ldbcではテーブル定義に複数`PRIMARY_KEY`を設定したとしてもコンパイルエラーにすることはできません。しかし、テーブル定義をクエリの生成やドキュメントの生成などで使用する場合エラーとなります。これはPRIMARY KEYはテーブルごとに1つしか設定することができないという制約によるものです。 - -```scala 3 -val table = Table[User]("user")( - column("id", INT, AUTO_INCREMENT, PRIMARY_KEY), - column("name", VARCHAR(50), PRIMARY_KEY), - column("email", VARCHAR(100)) -) - -// CREATE TABLE `user` ( -// `id` BIGINT AUTO_INCREMENT PRIMARY KEY, -// ) -``` - -### UNIQUE KEY - -一意キー(unique key)とはMySQLにおいてデータを一意に識別するための項目のことです。カラムに一意性制約を設定すると、カラムには他のデータの値を重複することのない値しか格納することができなくなります。 - -ldbcではこの一意性制約を2つの方法で設定することができます。 - -1. columnメソッドの属性として設定する -2. tableのkeySetメソッドで設定する - -**columnメソッドの属性として設定する** - -columnメソッドの属性として設定する方法は非常に簡単で、columnメソッドの第3引数以降に`UNIQUE_KEY`を渡すだけです。これによって以下の場合 `id`カラムを一意キーとして設定することができます。 - -```scala 3 -column("id", BIGINT, AUTO_INCREMENT, UNIQUE_KEY) -``` - -**tableのkeySetメソッドで設定する** - -ldbcのテーブル定義には `keySet`というメソッドが生えており、ここで`UNIQUE_KEY`に一意キーとして設定したいカラムを渡すことで一意キーとして設定することができます。 - -```scala 3 -val table = Table[User]("user")( - column("id", INT, AUTO_INCREMENT, PRIMARY_KEY), - column("name", VARCHAR(50)), - column("email", VARCHAR(100)) -) - .keySet(table => UNIQUE_KEY(table.id)) - -// CREATE TABLE `user` ( -// ..., -// UNIQUE KEY (`id`) -// ) -``` - -`UNIQUE_KEY`メソッドにはカラム意外にも以下のパラメーターを設定することができます。 - -- `Index Name` String -- `Index Type` ldbc.schema.Index.Type.BTREE or ldbc.schema.Index.Type.HASH -- `Index Option` ldbc.schema.Index.IndexOption - -#### 複合キー (unique key) - -1つのカラムだけではなく、複数のカラムを一意キーとして組み合わせ一意キーとして設定することもできます。`UNIQUE_KEY`に一意キーとして設定したいカラムを複数渡すだけで複合一意キーとして設定することができます。 - -```scala 3 -val table = Table[User]("user")( - column("id", INT, AUTO_INCREMENT, PRIMARY_KEY), - column("name", VARCHAR(50)), - column("email", VARCHAR(100)) -) - .keySet(table => UNIQUE_KEY(table.id, table.name)) - -// CREATE TABLE `user` ( -// ..., -// UNIQUE KEY (`id`, `name`) -// ) -``` - -複合キーは`keySet`メソッドでの`UNIQUE_KEY`でしか設定することはできません。仮にcolumnメソッドの属性として複数設定を行うと複合キーとしてではなく、それぞれを一意キーとして設定されてしまいます。 - -### INDEX KEY - -インデックスキー(index key)とはMySQLにおいて目的のレコードを効率よく取得するための「索引」のことです。 - -ldbcではこのインデックスを2つの方法で設定することができます。 - -1. columnメソッドの属性として設定する -2. tableのkeySetメソッドで設定する - -**columnメソッドの属性として設定する** - -columnメソッドの属性として設定する方法は非常に簡単で、columnメソッドの第3引数以降に`INDEX_KEY`を渡すだけです。これによって以下の場合 `id`カラムをインデックスとして設定することができます。 - -```scala 3 -column("id", BIGINT, AUTO_INCREMENT, INDEX_KEY) -``` - -**tableのkeySetメソッドで設定する** - -ldbcのテーブル定義には `keySet`というメソッドが生えており、ここで`INDEX_KEY`にインデックスとして設定したいカラムを渡すことでインデックスキーとして設定することができます。 - -```scala 3 -val table = Table[User]("user")( - column("id", INT, AUTO_INCREMENT, PRIMARY_KEY), - column("name", VARCHAR(50)), - column("email", VARCHAR(100)) -) - .keySet(table => INDEX_KEY(table.id)) - -// CREATE TABLE `user` ( -// ..., -// INDEX KEY (`id`) -// ) -``` - -`INDEX_KEY`メソッドにはカラム意外にも以下のパラメーターを設定することができます。 - -- `Index Name` String -- `Index Type` ldbc.schema.Index.Type.BTREE or ldbc.schema.Index.Type.HASH -- `Index Option` ldbc.schema.Index.IndexOption - -#### 複合キー (index key) - -1つのカラムだけではなく、複数のカラムをインデックスキーとして組み合わせインデックスキーとして設定することもできます。`INDEX_KEY`にインデックスキーとして設定したいカラムを複数渡すだけで複合インデックスとして設定することができます。 - -```scala 3 -val table = Table[User]("user")( - column("id", INT, AUTO_INCREMENT, PRIMARY_KEY), - column("name", VARCHAR(50)), - column("email", VARCHAR(100)) -) - .keySet(table => INDEX_KEY(table.id, table.name)) - -// CREATE TABLE `user` ( -// ..., -// INDEX KEY (`id`, `name`) -// ) -``` - -複合キーは`keySet`メソッドでの`INDEX_KEY`でしか設定することはできません。仮にcolumnメソッドの属性として複数設定を行うと複合インデックスとしてではなく、それぞれをインデックスキーとして設定されてしまいます。 - -### FOREIGN KEY - -外部キー(foreign key)とは、MySQLにおいてデータの整合性を保つための制約(参照整合性制約)です。 外部キーに設定されているカラムには、参照先となるテーブルのカラム内に存在している値しか設定できません。 - -ldbcではこの外部キー制約をtableのkeySetメソッドを使用する方法で設定することができます。 - -```scala 3 -val user = Table[User]("user")( - column("id", INT, AUTO_INCREMENT, PRIMARY_KEY), - column("name", VARCHAR(50)), - column("email", VARCHAR(100)) -) - -val order = Table[Order]("order")( - column("id", INT, AUTO_INCREMENT, PRIMARY_KEY), - column("user_id", VARCHAR(50)) - ... -) - .keySet(table => FOREIGN_KEY(table.userId, REFERENCE(user, user.id))) - -// CREATE TABLE `order` ( -// ..., -// FOREIGN KEY (user_id) REFERENCES `user` (id), -// ) -``` - -`FOREIGN_KEY`メソッドにはカラムとReference値意外にも以下のパラメーターを設定することができます。 - -- `Index Name` String - -外部キー制約には親テーブルの削除時と更新時の挙動を設定することができます。`REFERENCE`メソッドに`onDelete`と`onUpdate`メソッドが提供されているのでこちらを使用することでそれぞれ設定することができます。 - -設定することのできる値は`ldbc.schema.Reference.ReferenceOption`から取得することができます。 - -```scala 3 -val order = Table[Order]("order")( - column("id", INT, AUTO_INCREMENT, PRIMARY_KEY), - column("user_id", VARCHAR(50)) - ... -) - .keySet(table => FOREIGN_KEY(table.userId, REFERENCE(user, user.id).onDelete(Reference.ReferenceOption.RESTRICT))) - -// CREATE TABLE `order` ( -// ..., -// FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT -// ) -``` - -設定することのできる値は以下になります。 - -- `RESTRICT`: 親テーブルに対する削除または更新操作を拒否します。 -- `CASCADE`: 親テーブルから行を削除または更新し、子テーブル内の一致する行を自動的に削除または更新します。 -- `SET_NULL`: 親テーブルから行を削除または更新し、子テーブルの外部キーカラムを NULL に設定します。 -- `NO_ACTION`: 標準 SQL のキーワード。 MySQLでは、RESTRICT と同等です。 -- `SET_DEFAULT`: このアクションは MySQL パーサーによって認識されますが、InnoDB と NDB はどちらも、ON DELETE SET DEFAULT または ON UPDATE SET DEFAULT 句を含むテーブル定義を拒否します。 - -#### 複合キー (foreign key) - -1つのカラムだけではなく、複数のカラムを外部キーとして組み合わせて設定することもできます。`FOREIGN_KEY`に外部キーとして設定したいカラムを複数渡すだけで複合外部キーとして設定することができます。 - -```scala 3 -val user = Table[User]("user")( - column("id", INT, AUTO_INCREMENT, PRIMARY_KEY), - column("name", VARCHAR(50)), - column("email", VARCHAR(100)) -) - -val order = Table[Order]("order")( - column("id", INT, AUTO_INCREMENT, PRIMARY_KEY), - column("user_id", VARCHAR(50)) - column("user_email", VARCHAR(100)) - ... -) - .keySet(table => FOREIGN_KEY((table.userId, table.userEmail), REFERENCE(user, (user.id, user.email)))) - -// CREATE TABLE `user` ( -// ..., -// FOREIGN KEY (`user_id`, `user_email`) REFERENCES `user` (`id`, `email`) -// ) -``` - -### 制約名 - -MySQLではCONSTRAINTを使用することで制約に対して任意の名前を付与することができます。この制約名はデータベース単位で一意の値である必要があります。 - -ldbcではCONSTRAINTメソッドが提供されているのでキー制約などの制約を設定する処理をCONSTRAINTメソッドに渡すだけで設定することができます。 - -```scala 3 -val order = Table[Order]("order")( - column("id", INT, AUTO_INCREMENT, PRIMARY_KEY), - column("user_id", VARCHAR(50)) - ... -) - .keySet(table => CONSTRAINT("fk_user_id", FOREIGN_KEY(table.userId, REFERENCE(user, user.id)))) - -// CREATE TABLE `order` ( -// ..., -// CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) -// ) -``` - -## カスタム データ型 - -ユーザー独自の型もしくはサポートされていない型を使用するための方法はカラムのデータ型をどのような型として扱うかを教えてあげることです。DataTypeには`mapping`メソッドが提供されているのでこのメソッドを使用して暗黙の型変換として設定します。 - -```scala 3 -case class User( - id: Int, - name: User.Name, - email: String, -) - -object User: - - case class Name(firstName: String, lastName: String) - - given Conversion[VARCHAR[String], DataType[Name]] = DataType.mapping[VARCHAR[String], Name] - - val table = Table[User]("user")( - column("id", INT, AUTO_INCREMENT), - column("name", VARCHAR(50)), - column("email", VARCHAR(100)) - ) -``` - -ldbcでは複数のカラムをモデルが持つ1つのプロパティに統合することはできません。ldbcの目的はモデルとテーブルを1対1でマッピングを行い、データベースのテーブル定義を型安全に構築することにあるからです。 - -そのためテーブル定義とモデルで異なった数のプロパティを持つようなことは許可していません。以下のような実装はコンパイルエラーとなります。 - -```scala 3 -case class User( - id: Int, - name: User.Name, - email: String, -) - -object User: - - case class Name(firstName: String, lastName: String) - - val table = Table[User]("user")( - column("id", INT, AUTO_INCREMENT), - column("first_name", VARCHAR(50)), - column("last_name", VARCHAR(50)), - column("email", VARCHAR(100)) - ) -``` - -上記のような実装を行いたい場合は以下のような実装を検討してください。 - -```scala 3 -case class User( - id: Int, - firstName: String, - lastName: String, - email: String, -): - - val name: User.Name = User.Name(firstName, lastName) - -object User: - - case class Name(firstName: String, lastName: String) - - val table = Table[User]("user")( - column("id", INT, AUTO_INCREMENT), - column("first_name", VARCHAR(50)), - column("last_name", VARCHAR(50)), - column("email", VARCHAR(100)) - ) -``` diff --git a/docs/src/main/mdoc/ja/tutorial/Selecting-Data.md b/docs/src/main/mdoc/ja/tutorial/Selecting-Data.md index db3d9eb43..76cf3426d 100644 --- a/docs/src/main/mdoc/ja/tutorial/Selecting-Data.md +++ b/docs/src/main/mdoc/ja/tutorial/Selecting-Data.md @@ -14,7 +14,7 @@ ```scala sql"SELECT name FROM user" .query[String] // Query[IO, String] - .to[List] // Executor[IO, List[String]] + .to[List] // DBIO[IO, List[String]] .readOnly(conn) // IO[List[String]] .unsafeRunSync() // List[String] .foreach(println) // Unit @@ -23,7 +23,7 @@ sql"SELECT name FROM user" これを少し分解してみよう。 - `sql"SELECT name FROM user".query[String]`は`Query[IO, String]`を定義し、返される各行をStringにマップする1列のクエリです。このクエリは1列のクエリで、返される行をそれぞれStringにマップします。 -- `.to[List]`は、行をリストに蓄積する便利なメソッドで、この場合は`Executor[IO, List[String]]`を生成します。このメソッドは、CanBuildFromを持つすべてのコレクション・タイプで動作します。 +- `.to[List]`は、行をリストに蓄積する便利なメソッドで、この場合は`DBIO[IO, List[String]]`を生成します。このメソッドは、CanBuildFromを持つすべてのコレクション・タイプで動作します。 - `readOnly(conn)`は`IO[List[String]]`を生成し、これを実行すると通常のScala `List[String]`が出力される。 - `unsafeRunSync()`は、IOモナドを実行し、結果を取得する。これは、IOモナドを実行し、結果を取得するために使用される。 - `foreach(println)`は、リストの各要素をプリントアウトする。 @@ -35,7 +35,7 @@ sql"SELECT name FROM user" ```scala sql"SELECT name, email FROM user" .query[(String, String)] // Query[IO, (String, String)] - .to[List] // Executor[IO, List[(String, String)]] + .to[List] // DBIO[IO, List[(String, String)]] .readOnly(conn) // IO[List[(String, String)]] .unsafeRunSync() // List[(String, String)] .foreach(println) // Unit @@ -50,7 +50,7 @@ case class User(id: Long, name: String, email: String) sql"SELECT id, name, email FROM user" .query[User] // Query[IO, User] - .to[List] // Executor[IO, List[User]] + .to[List] // DBIO[IO, List[User]] .readOnly(conn) // IO[List[User]] .unsafeRunSync() // List[User] .foreach(println) // Unit @@ -80,7 +80,7 @@ sql""" JOIN country ON city.country_code = country.code """ .query[CityWithCountry] // Query[IO, CityWithCountry] - .to[List] // Executor[IO, List[CityWithCountry]] + .to[List] // DBIO[IO, List[CityWithCountry]] .readOnly(conn) // IO[List[CityWithCountry]] .unsafeRunSync() // List[CityWithCountry] .foreach(println) // Unit diff --git a/docs/src/main/mdoc/ja/tutorial/Setup.md b/docs/src/main/mdoc/ja/tutorial/Setup.md index 5faa2ae89..5b51337b8 100644 --- a/docs/src/main/mdoc/ja/tutorial/Setup.md +++ b/docs/src/main/mdoc/ja/tutorial/Setup.md @@ -133,17 +133,17 @@ import cats.effect.* 以下のコードは、トレーサーとログハンドラーを提供するがその実体は何もしない。 -```scala +```scala 3 given Tracer[IO] = Tracer.noop[IO] given LogHandler[IO] = LogHandler.noop[IO] ``` -ldbc高レベルAPIで扱う最も一般的な型は`Executor[F, A]`という形式で、`{java | ldbc}.sql.Connection`が利用可能なコンテキストで行われる計算を指定し、最終的にA型の値を生成します。 +ldbc高レベルAPIで扱う最も一般的な型は`DBIO[F, A]`という形式で、`{java | ldbc}.sql.Connection`が利用可能なコンテキストで行われる計算を指定し、最終的にA型の値を生成します。 -では、定数を返すだけのExecutorプログラムから始めてみよう。 +では、定数を返すだけのDBIOプログラムから始めてみよう。 ```scala -val program: Executor[IO, Int] = Executor.pure[IO, Int](1) +val program: DBIO[IO, Int] = DBIO.pure[IO, Int](1) ``` 次に、データベースに接続するためのコネクタを作成する。コネクタは、データベースへの接続を管理するためのリソースである。コネクタは、データベースへの接続を開始し、クエリを実行し、接続を閉じるためのリソースを提供する。 @@ -160,7 +160,7 @@ def connection = Connection[IO]( ) ``` -Executorは、データベースへの接続方法、接続の受け渡し方法、接続のクリーンアップ方法を知っているデータ型であり、この知識によってExecutorをIOへ変換し、実行可能なプログラムを得ることができる。具体的には、実行するとデータベースに接続し、単一のトランザクションを実行するIOが得られる。 +DBIOは、データベースへの接続方法、接続の受け渡し方法、接続のクリーンアップ方法を知っているデータ型であり、この知識によってExecutorをIOへ変換し、実行可能なプログラムを得ることができる。具体的には、実行するとデータベースに接続し、単一のトランザクションを実行するIOが得られる。 ```scala connection diff --git a/docs/src/main/mdoc/ja/tutorial/Simple-Program.md b/docs/src/main/mdoc/ja/tutorial/Simple-Program.md index 2cc7d8517..022fcec19 100644 --- a/docs/src/main/mdoc/ja/tutorial/Simple-Program.md +++ b/docs/src/main/mdoc/ja/tutorial/Simple-Program.md @@ -15,8 +15,8 @@ それでは、`sql string interpolator`を使って、データベースに定数の計算を依頼する問い合わせを作成してみましょう。 -```scala -val program: Executor[IO, Option[Int]] = sql"SELECT 2".query[Int].to[Option] +```scala 3 +val program: DBIO[Option[Int]] = sql"SELECT 2".query[Int].to[Option] ``` `sql string interpolator`を使って作成したクエリは`query`メソッドで取得する型の決定を行います。ここでは`Int`型を取得するため、`query[Int]`としています。また、`to`メソッドで取得する型を決定します。ここでは`Option`型を取得するため、`to[Option]`としています。 @@ -29,7 +29,7 @@ val program: Executor[IO, Option[Int]] = sql"SELECT 2".query[Int].to[Option] 最後に、データベースに接続して値を返すプログラムを書きます。このプログラムは、データベースに接続し、クエリを実行し、結果を取得します。 -```scala +```scala 3 connection .use { conn => program.readOnly(conn).map(println(_)) @@ -49,10 +49,10 @@ scala-cli https://github.com/takapi327/ldbc/tree/master/docs/src/main/scala/02-P ## 2つめのプログラム -一つの取引で複数のことをしたい場合はどうすればいいのか?簡単だ!Executorはモナドなので、for内包を使って2つの小さなプログラムを1つの大きなプログラムにすることができる。 +一つの取引で複数のことをしたい場合はどうすればいいのか?簡単だ!DBIOはモナドなので、for内包を使って2つの小さなプログラムを1つの大きなプログラムにすることができる。 -```scala -val program: Executor[IO, (List[Int], Option[Int], Int)] = +```scala 3 +val program: DBIO[(List[Int], Option[Int], Int)] = for result1 <- sql"SELECT 1".query[Int].to[List] result2 <- sql"SELECT 2".query[Int].to[Option] @@ -62,7 +62,7 @@ val program: Executor[IO, (List[Int], Option[Int], Int)] = 最後に、データベースに接続して値を返すプログラムを書く。このプログラムは、データベースに接続し、クエリを実行し、結果を取得する。 -```scala +```scala 3 connection .use { conn => program.readOnly(conn).map(println(_)) @@ -82,14 +82,14 @@ scala-cli https://github.com/takapi327/ldbc/tree/master/docs/src/main/scala/03-P データベースに対して書き込みを行うプログラムを書いてみよう。ここでは、データベースに接続し、クエリを実行し、データを挿入する。 -```scala -val program: Executor[IO, Int] = +```scala 3 +val program: DBIO[Int] = sql"INSERT INTO user (name, email) VALUES ('Carol', 'carol@example.com')".update ``` 先ほどと異なる点は、`commit`メソッドを呼び出すことである。これにより、トランザクションがコミットされ、データベースにデータが挿入される。 -```scala +``` 3 connection .use { conn => program.commit(conn).map(println(_)) diff --git a/docs/src/main/mdoc/ja/tutorial/Updating-Data.md b/docs/src/main/mdoc/ja/tutorial/Updating-Data.md index b7b8ac1ad..60e5690f9 100644 --- a/docs/src/main/mdoc/ja/tutorial/Updating-Data.md +++ b/docs/src/main/mdoc/ja/tutorial/Updating-Data.md @@ -9,10 +9,10 @@ ## 挿入 -挿入は簡単で、selectと同様に動作します。ここでは、`user`テーブルに行を挿入する`Executor`を作成するメソッドを定義します。 +挿入は簡単で、selectと同様に動作します。ここでは、`user`テーブルに行を挿入する`DBIO`を作成するメソッドを定義します。 ```scala -def insertUser(name: String, email: String): Executor[IO, Int] = +def insertUser(name: String, email: String): DBIO[Int] = sql"INSERT INTO user (name, email) VALUES ($name, $email)" .update ``` @@ -28,7 +28,7 @@ insertUser("dave", "dave@example.com").commit.unsafeRunSync() ```scala sql"SELECT * FROM user" .query[(Int, String, String)] // Query[IO, (Int, String, String)] - .to[List] // Executor[IO, List[(Int, String, String)]] + .to[List] // DBIO[IO, List[(Int, String, String)]] .readOnly(conn) // IO[List[(Int, String, String)]] .unsafeRunSync() // List[(Int, String, String)] .foreach(println) // Unit @@ -39,7 +39,7 @@ sql"SELECT * FROM user" 更新も同じパターンだ。ここではユーザーのメールアドレスを更新する。 ```scala -def updateUserEmail(id: Int, email: String): Executor[IO, Int] = +def updateUserEmail(id: Int, email: String): DBIO[Int] = sql"UPDATE user SET email = $email WHERE id = $id" .update ``` @@ -51,7 +51,7 @@ updateUserEmail(1, "alice+1@example.com").commit.unsafeRunSync() sql"SELECT * FROM user WHERE id = 1" .query[(Int, String, String)] // Query[IO, (Int, String, String)] - .to[Option] // Executor[IO, List[(Int, String, String)]] + .to[Option] // DBIO[IO, List[(Int, String, String)]] .readOnly(conn) // IO[List[(Int, String, String)]] .unsafeRunSync() // List[(Int, String, String)] .foreach(println) // Unit @@ -62,8 +62,8 @@ sql"SELECT * FROM user WHERE id = 1" インサートする際には、新しく生成されたキーを返したいものです。まず、挿入して最後に生成されたキーを`LAST_INSERT_ID`で取得し、指定された行を選択するという難しい方法をとります。 -```scala -def insertUser(name: String, email: String): Executor[IO, (Int, String, String)] = +```scala 3 +def insertUser(name: String, email: String): DBIO[IO, (Int, String, String)] = for _ <- sql"INSERT INTO user (name, email) VALUES ($name, $email)".update id <- sql"SELECT LAST_INSERT_ID()".query[Int].unsafe @@ -81,8 +81,8 @@ MySQLでは、`AUTO_INCREMENT`が設定された行のみが挿入時に返す 自動生成キーを使用して行を挿入する場合、`returning`メソッドを使用して自動生成キーを取得できます。 -```scala -def insertUser(name: String, email: String): Executor[IO, (Int, String, String)] = +```scala 3 +def insertUser(name: String, email: String): DBIO[IO, (Int, String, String)] = for id <- sql"INSERT INTO user (name, email) VALUES ($name, $email)".returning[Int] user <- sql"SELECT * FROM user WHERE id = $id".query[(Int, String, String)].to[Option] @@ -97,8 +97,8 @@ insertUser("frank", "frank@example.com").commit.unsafeRunSync() バッチ更新を行うには、`NonEmptyList`を使用して複数の行を挿入する`insertManyUser`メソッドを定義します。 -```scala -def insertManyUser(users: NonEmptyList[(String, String)]): Executor[IO, Int] = +```scala 3 +def insertManyUser(users: NonEmptyList[(String, String)]): DBIO[IO, Int] = val value = users.map { case (name, email) => sql"($name, $email)" } (sql"INSERT INTO user (name, email) VALUES" ++ values(value)).update ``` diff --git a/docs/src/main/mdoc/ja/tutorial/directory.conf b/docs/src/main/mdoc/ja/tutorial/directory.conf index a95cc9a92..356888c67 100644 --- a/docs/src/main/mdoc/ja/tutorial/directory.conf +++ b/docs/src/main/mdoc/ja/tutorial/directory.conf @@ -1,4 +1,4 @@ -laika.title = Tutorial +laika.title = チュートリアル laika.navigationOrder = [ index.md, Setup.md,