Skip to content

Commit

Permalink
Merge branch 'document/2024-10-Update-document-for-v0.3' of github.co…
Browse files Browse the repository at this point in the history
…m:takapi327/ldbc into document/2024-10-Update-document-for-v0.3
  • Loading branch information
takapi327 committed Feb 9, 2025
2 parents 0d17c30 + b403295 commit 30a6b63
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 466 deletions.
134 changes: 102 additions & 32 deletions docs/src/main/mdoc/ja/migration-notes.md
Original file line number Diff line number Diff line change
@@ -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への移行)

## パッケージ

Expand Down Expand Up @@ -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型しか返すことができません。
Expand All @@ -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)
Expand All @@ -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]
```

### 列の絞り込み方法の変更
Expand Down
110 changes: 104 additions & 6 deletions docs/src/main/mdoc/ja/tutorial/Custom-Data-Type.md
Original file line number Diff line number Diff line change
Expand Up @@ -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" }, ${ "[email protected]" }, ${ 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]
```
8 changes: 4 additions & 4 deletions docs/src/main/mdoc/ja/tutorial/Database-Operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,18 @@ 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]
result3 <- sql"SELECT 3".query[Int].unsafe
yield (result1, result2, result3)
```

1つのプログラムとなった`Executor``transaction`メソッドで1つのトランザクションでまとめて処理を行うことができます。
1つのプログラムとなった`DBIO``transaction`メソッドで1つのトランザクションでまとめて処理を行うことができます。

```scala
val transaction = program.transaction(connection)
Expand Down
8 changes: 4 additions & 4 deletions docs/src/main/mdoc/ja/tutorial/Error-Handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

## 例外について

ある操作が成功するかどうかは、ネットワークの健全性、テーブルの現在の内容、ロックの状態など、予測できない要因に依存します。そのため、`EitherT[Executor, Throwable, A]`のような論理和ですべてを計算するか、明示的に捕捉されるまで例外の伝播を許可するかを決めなければならない。つまり、ldbcのアクション(ターゲット・モナドに変換される)が実行されると、例外が発生する可能性がある。
ある操作が成功するかどうかは、ネットワークの健全性、テーブルの現在の内容、ロックの状態など、予測できない要因に依存します。そのため、`EitherT[DBIO, Throwable, A]`のような論理和ですべてを計算するか、明示的に捕捉されるまで例外の伝播を許可するかを決めなければならない。つまり、ldbcのアクション(ターゲット・モナドに変換される)が実行されると、例外が発生する可能性がある。

発生しやすい例外は主に3種類ある

Expand All @@ -19,7 +19,7 @@

## モナド・エラーと派生コンバイネーター

すべてのldbcモナドは、`MonadError[?[_], Throwable]`を拡張したAsyncインスタンスを提供する。つまり、Executorなどは以下のようなプリミティブな操作を持つことになる
すべてのldbcモナドは、`MonadError[?[_], Throwable]`を拡張したAsyncインスタンスを提供する。つまり、DBIOなどは以下のようなプリミティブな操作を持つことになる

- raiseError: 例外を発生させる (Throwableを`M[A]`に変換する)
- handleErrorWith: 例外を処理する (`M[A]``M[B]`に変換する)
Expand All @@ -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のドキュメントで説明されているように、他の多くの操作を派生させることができます。
7 changes: 5 additions & 2 deletions docs/src/main/mdoc/ja/tutorial/Schema-Code-Generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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を実装します。

Expand All @@ -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に変換してあげます。
Expand Down
Loading

0 comments on commit 30a6b63

Please sign in to comment.