diff --git a/README.md b/README.md index b987eac4f..ae200f937 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,20 @@ Note that **ldbc** is pre-1.0 software and is still undergoing active developmen Please drop a :star: if this project interests you. I need encouragement. +## Modules availability + +| Module / Platform | JVM | Scala Native | Scala.js | +|:--------------------:|:---:|:------------:|:--------:| +| `ldbc-core` | ✅ | ✅ | ✅ | +| `ldbc-sql` | ✅ | ✅ | ✅ | +| `ldbc-query-builder` | ✅ | ✅ | ✅ | +| `ldbc-dsl` | ✅ | ❌ | ❌ | +| `ldbc-schemaSpy` | ✅ | ❌ | ❌ | +| `ldbc-codegen` | ✅ | ✅ | ✅ | +| `ldbc-hikari` | ✅ | ❌ | ❌ | +| `ldbc-plugin` | ✅ | ❌ | ❌ | +| `ldbc-connector` | ✅ | ✅ | ✅ | + ## Documentation - [English](https://takapi327.github.io/ldbc/en/index.html) diff --git a/docs/src/main/mdoc/en/09-Connector.md b/docs/src/main/mdoc/en/09-Connector.md new file mode 100644 index 000000000..37b4fd604 --- /dev/null +++ b/docs/src/main/mdoc/en/09-Connector.md @@ -0,0 +1,405 @@ +# Connector + +This chapter describes database connections using LDBC's own MySQL connector. + +To make a connection to a MySQL database in Scala, you need to use JDBC, which is a standard Java API that can also be used in Scala. +JDBC is implemented in Java and can only work in a JVM environment, even when used in Scala. + +The recent environment surrounding Scala has seen a lot of development of plug-ins to work with JS, Native, and other environments. +Scala continues to evolve from a language that runs only in the JVM, where Java assets can be used, to one that can run in a multi-platform environment. + +However, JDBC is a standard Java API and does not support operation in Scala's multiplatform environment. + +Therefore, even if you create an application in Scala that can run on JS, Native, etc., you will not be able to connect to databases such as MySQL because you cannot use JDBC. + +Typelevel Project has a Scala library for [PostgreSQL](https://www.postgresql.org/) called [Skunk](https://github.com/typelevel/skunk). +This project does not use JDBC and uses only pure Scala to connect to PostgreSQL. Therefore, Skunk can be used to connect to PostgreSQL in any JVM, JS, or Native environment. + +The LDBC connector is a Skunk-inspired project that is being developed to enable connections to MySQL in any JVM, JS, or Native environment. + +※ This connector is currently an experimental feature. Therefore, please do not use it in a production environment. + +The LDBC connector is the lowest layer API. +We plan to use this connector to provide higher-layer APIs in the future. We also plan to make it compatible with existing higher-layer APIs. + +The following dependencies must be set up in your project in order to use it. + +**JVM** + +@@@ vars +```scala +libraryDependencies += "$org$" %% "ldbc-connector" % "$version$" +``` +@@@ + +**JS/Native** + +@@@ vars +```scala +libraryDependencies += "$org$" %%% "ldbc-connector" % "$version$" +``` +@@@ + +**Supported Versions** + +The current version supports the following versions of MySQL + +- MySQL 5.7.x +- MySQL 8.x + +The main support is for MySQL 8.x. MySQL 5.7.x is a sub-support. Therefore, be careful when working with MySQL 5.7.x. +We plan to discontinue support for MySQL 5.7.x in the future. + +## Connection + +Use `Connection` to make a connection to MySQL using the LDBC connector. + +In addition, `Connection` allows the use of `Otel4s` to collect telemetry data in order to allow observer-aware development. +Therefore, when using `Connection`, the `Tracer` of `Otel4s` must be set. + +It is recommended to use `Tracer.noop` during development or when telemetry data using traces is not needed. + +```scala +import cats.effect.IO +import org.typelevel.otel4s.trace.Tracer +import ldbc.connector.Connection + +given Tracer[IO] = Tracer.noop[IO] + +val connection = Connection[IO]( + host = "127.0.0.1", + port = 3306, + user = "root", +) +``` + +The following is a list of properties that can be set when constructing a `Connection`. + +| Property | Type | Use | +|-------------------------|--------------------|------------------------------------------------------------------------------------------------------------| +| host | String | Specify the host for the MySQL server | +| port | Int | Specify the port number of the MySQL server | +| user | String | Specify the user name to log in to the MySQL server | +| password | Option[String] | Specify the password of the user who will log in to the MySQL server | +| database | Option[String] | Specify the database name to be used after connecting to the MySQL server | +| debug | Boolean | Outputs a log of the process. Default is false. | +| ssl | SSL | Specifies whether SSL/TLS is used for notifications to and from the MySQL server. The default is SSL.None. | +| socketOptions | List[SocketOption] | Specifies socket options for TCP/UDP sockets. | +| readTimeout | Duration | Specifies the timeout before an attempt is made to connect to the MySQL server. Default is Duration.Inf. | +| allowPublicKeyRetrieval | Boolean | Specifies whether to use the RSA public key when authenticating with the MySQL server. Default is false. | + +Connection` uses `Resource` to manage resources. Therefore, when connection information is used, the `use` method is used to manage the resource. + +```scala +connection.use { conn => + // Write code +} +``` + +### Authentication + +Authentication in MySQL involves the client sending user information in a phase called LoginRequest when connecting to the MySQL server. The server then looks up the user in the `mysql.user` table to determine which authentication plugin to use. After the authentication plugin is determined, the server calls the plugin to initiate user authentication and sends the results to the client. In this way, authentication is pluggable (various types of plug-ins can be added and removed) in MySQL. + +Authentication plug-ins supported by MySQL are listed on the [official page](https://dev.mysql.com/doc/refman/8.0/ja/authentication-plugins.html). + +LDBC currently supports the following authentication plug-ins + +- Native pluggable authentication +- SHA-256 pluggable authentication +- Cache of SHA-2 pluggable certificates + +※ Native pluggable authentication and SHA-256 pluggable authentication are plugins that have been deprecated since MySQL 8.x. It is recommended that you use the SHA-2 pluggable authentication cache unless you have a good reason to do otherwise. + +There is no need to be aware of authentication plug-ins in the LDBC application code. Users simply create a user created with the authentication plugin they wish to use on the MySQL database and then attempt to connect to MySQL using that user in the LDBC application code. +LDBC will internally determine the authentication plugin and use the appropriate authentication plugin to connect to MySQL. + +## Execution + +The following tables are assumed to be used in the subsequent process. + +```sql +CREATE TABLE users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + age INT NULL +); +``` + +### Statement + +`Statement` is an API for executing SQL without dynamic parameters. + +※ Since `Statement` does not use dynamic parameters, there is a risk of SQL injection depending on its usage. Therefore, it is recommended to use `PreparedStatement` when dynamic parameters are used. + +Construct a `Statement` using the `createStatement` method of `Connection`. + +#### Read query + +Use the `executeQuery` method to execute read-only SQL. + +The values returned by the MySQL server as a result of executing the query are stored in a `ResultSet` and returned as the return value. + +```scala +connection.use { conn => + val statement: Statement[IO] = conn.createStatement("SELECT * FROM users") + val result: IO[ResultSet] = statement.executeQuery() + // Processing with ResultSet +} +``` + +#### Write Query + +Use the `executeUpdate` method to execute SQL to write. + +The value returned by the MySQL server as a result of executing the query is the number of rows affected. + +```scala +connection.use { conn => + val statement: Statement[IO] = conn.createStatement("INSERT INTO users (name, age) VALUES ('Alice', 20)") + val result: IO[Int] = statement.executeUpdate() +} +``` + +#### Get the value of AUTO_INCREMENT + +Use the `returningAutoGeneratedKey` method to retrieve the AUTO_INCREMENT value after the query is executed using `Statement`. + +The value returned by the MySQL server as a result of executing the query will be the value generated for AUTO_INCREMENT as the return value. + +```scala +connection.use { conn => + val statement: Statement[IO] = conn.createStatement("INSERT INTO users (name, age) VALUES ('Alice', 20)") + val result: IO[Int] = statement.returningAutoGeneratedKey() +} +``` + +### Client/Server PreparedStatement + +LDBC provides `PreparedStatement` divided into `Client PreparedStatement` and `Server PreparedStatement`. + +`Client PreparedStatement` is an API for constructing SQL on the application using dynamic parameters and sending it to the MySQL server. +Therefore, the method of sending queries to the MySQL server is the same as for `Statement`. + +This API is equivalent to JDBC's `PreparedStatement`. + +A `PreparedStatement` for building queries in a more secure MySQL server is provided in the `Server PreparedStatement`, so please use that. + +`Server PreparedStatement` is an API that prepares the query to be executed in advance in the MySQL server and executes it by setting parameters in the application. + +The `Server PreparedStatement` allows reuse of queries, since the query to be executed and the parameters are sent separately. + +When using `Server PreparedStatement`, the query is prepared in advance by the MySQL server. Although the MySQL server uses memory to store them, the queries can be reused, which improves performance. + +However, there is a risk of memory leaks because the pre-prepared query will continue to use memory until it is freed. + +If you use `Server PreparedStatement`, you must use the `close` method to properly release the query. + +#### Client PreparedStatement + +Construct a `Client PreparedStatement` using the `ClientPreparedStatement` method of `Connection`. + +```scala +connection.use { conn => + for + statement <- conn.clientPreparedStatement("SELECT * FROM users WHERE id = ?") + ... + yield ... +} +``` + +#### Server PreparedStatement + +Construct a `Server PreparedStatement` using the `Connection` `serverPreparedStatement` method. + +```scala +connection.use { conn => + for + statement <- conn.serverPreparedStatement("SELECT * FROM users WHERE id = ?") + ... + yield ... +} +``` + +#### Read query + +Use the `executeQuery` method to execute read-only SQL. + +The values returned by the MySQL server as a result of executing the query are stored in a `ResultSet` and returned as the return value. + +```scala +connection.use { conn => + for + statement <- conn.clientPreparedStatement("SELECT * FROM users WHERE id = ?") // or conn.serverPreparedStatement("SELECT * FROM users WHERE id = ?") + _ <- statement.setLong(1, 1) + result <- statement.executeQuery() + yield + // Processing with ResultSet +} +``` + +If you want to use dynamic parameters, use the `setXXX` method to set the parameters. +The `setXXX` method can also use the `Option` type. If `None` is passed, the parameter will be set to NULL. + +The `setXXX` method specifies the index of the parameter and the value of the parameter. + +```scala +statement.setLong(1, 1) +``` + +The following methods are supported in the current version + +| Method | Type | Note | +|---------------|:------------------------------------|----------------------------------------------------| +| setNull | | Set the parameter to NULL | +| setBoolean | Boolean/Option[Boolean] | | +| setByte | Byte/Option[Byte] | | +| setShort | Short/Option[Short] | | +| setInt | Int/Option[Int] | | +| setLong | Long/Option[Long] | | +| setBigInt | BigInt/Option[BigInt] | | +| setFloat | Float/Option[Float] | | +| setDouble | Double/Option[Double] | | +| setBigDecimal | BigDecimal/Option[BigDecimal] | | +| setString | String/Option[String] | | +| setBytes | Array[Byte]/Option[Array[Byte]] | | +| setDate | LocalDate/Option[LocalDate] | Directly handle `java.time` instead of `java.sql`. | +| setTime | LocalTime/Option[LocalTime] | Directly handle `java.time` instead of `java.sql`. | +| setTimestamp | LocalDateTime/Option[LocalDateTime] | Directly handle `java.time` instead of `java.sql`. | +| setYear | Year/Option[Year] | Directly handle `java.time` instead of `java.sql`. | + +#### Write Query + +Use the `executeUpdate` method to execute the SQL to be written. + +The value returned by the MySQL server as a result of executing the query is the number of rows affected. + +```scala +connection.use { conn => + for + statement <- conn.clientPreparedStatement("INSERT INTO users (name, age) VALUES (?, ?)") // or conn.serverPreparedStatement("INSERT INTO users (name, age) VALUES (?, ?)") + _ <- statement.setString(1, "Alice") + _ <- statement.setInt(2, 20) + result <- statement.executeUpdate() + yield result +} + +``` + +#### Get the value of AUTO_INCREMENT + +Use the `returningAutoGeneratedKey` method to retrieve the value of AUTO_INCREMENT after executing the query. + +The value returned by the MySQL server as a result of executing the query will be the value generated for AUTO_INCREMENT as the return value. + +```scala +connection.use { conn => + for + statement <- conn.clientPreparedStatement("INSERT INTO users (name, age) VALUES (?, ?)") // or conn.serverPreparedStatement("INSERT INTO users (name, age) VALUES (?, ?)") + _ <- statement.setString(1, "Alice") + _ <- statement.setInt(2, 20) + result <- statement.returningAutoGeneratedKey() + yield result +} +``` + +### ResultSet + +The `ResultSet` is an API for storing values returned by the MySQL server after query execution. + +The `decode` method is used to retrieve records from the `ResultSet` after they have been retrieved by executing SQL. + +The `decode` method is an API for converting values retrieved from `ResultSet` to Scala types. + +The type to be converted is specified using the `*:` operator depending on the number of columns to be retrieved. + +The example shows how to retrieve the id, name, and age columns of the users table, specifying the type of each column. + +```scala +result.decode(bigint *: varchar *: int.opt) +``` + +If you want to get a NULL-allowed column, use the `opt` method to convert it to the `Option` type. +If the record is NULL, it can be retrieved as None. + +The sequence of events from query execution to record retrieval is as follows + +```scala +connection.use { conn => + for + statement <- conn.clientPreparedStatement("SELECT * FROM users WHERE id = ?") // or conn.serverPreparedStatement("SELECT * FROM users WHERE id = ?") + _ <- statement.setLong(1, 1) + result <- statement.executeQuery() + yield + val decodes: List[(Long, String, Option[Int])] = result.decode(bigint *: varchar *: int.opt) + ... +} +``` + +The records retrieved from a `ResultSet` will always be an array. +This is because a query in MySQL may always return multiple records. + +If you want to retrieve a single record, use the `head` or `headOption` method after the `decode` process. + +The following data types are supported in the current version + +| Codec | Data Type | Scala Type | +|-------------|-------------------|----------------| +| boolean | BOOLEAN | Boolean | +| tinyint | TINYINT | Byte | +| utinyint | unsigned TINYINT | Short | +| smallint | SMALLINT | Short | +| usmallint | unsigned SMALLINT | Int | +| int | INT | Int | +| uint | unsigned INT | Long | +| bigint | BIGINT | Long | +| ubigint | unsigned BIGINT | BigInt | +| float | FLOAT | Float | +| double | DOUBLE | Double | +| decimal | DECIMAL | BigDecimal | +| char | CHAR | String | +| varchar | VARCHAR | String | +| binary | BINARY | Array[Byte] | +| varbinary | VARBINARY | String | +| tinyblob | TINYBLOB | String | +| blob | BLOB | String | +| mediumblob | MEDIUMBLOB | String | +| longblob | LONGBLOB | String | +| tinytext | TINYTEXT | String | +| text | TEXT | String | +| mediumtext | MEDIUMTEXT | String | +| longtext | LONGTEXT | String | +| enum | ENUM | String | +| set | SET | List[String] | +| json | JSON | String | +| date | DATE | LocalDate | +| time | TIME | LocalTime | +| timetz | TIME | OffsetTime | +| datetime | DATETIME | LocalDateTime | +| timestamp | TIMESTAMP | LocalDateTime | +| timestamptz | TIMESTAMP | OffsetDateTime | +| year | YEAR | Year | + +※ Currently, it is designed to retrieve values by specifying the MySQL data type, but in the future it may be changed to a more concise Scala type to retrieve values. + +The following data types are not supported + +- GEOMETRY +- POINT +- LINESTRING +- POLYGON +- MULTIPOINT +- MULTILINESTRING +- MULTIPOLYGON +- GEOMETRYCOLLECTION + +## Unsupported Feature + +The LDBC connector is currently an experimental feature. Therefore, the following features are not supported. +We plan to provide the features as they become available. + +- Connection Pooling +- Transaction +- Batch Processing +- Save points +- Failover measures +- etc... diff --git a/docs/src/main/mdoc/en/index.md b/docs/src/main/mdoc/en/index.md index 8ec3acaac..b725ba5b1 100644 --- a/docs/src/main/mdoc/en/index.md +++ b/docs/src/main/mdoc/en/index.md @@ -7,6 +7,7 @@ * [Generating SchemaSPY Documentation](./06-Generating-SchemaSPY-Documentation.md) * [Schema Code Generation](./07-Schema-Code-Generation.md) * [Performance](./08-Perdormance.md) + * [Connector](./09-Connector.md) @@@ # LDBC diff --git a/docs/src/main/mdoc/index.md b/docs/src/main/mdoc/index.md index 8c0f0fe34..86912754a 100644 --- a/docs/src/main/mdoc/index.md +++ b/docs/src/main/mdoc/index.md @@ -27,6 +27,20 @@ ldbc allows the same type-safe construction with Scala at the database layer and Note that **ldbc** is pre-1.0 software and is still undergoing active development. New versions are **not** binary compatible with prior versions, although in most cases user code will be source compatible. +## Modules availability + +| Module / Platform | JVM | Scala Native | Scala.js | +|:--------------------:|:---:|:------------:|:--------:| +| `ldbc-core` | ✅ | ✅ | ✅ | +| `ldbc-sql` | ✅ | ✅ | ✅ | +| `ldbc-query-builder` | ✅ | ✅ | ✅ | +| `ldbc-dsl` | ✅ | ❌ | ❌ | +| `ldbc-schemaSpy` | ✅ | ❌ | ❌ | +| `ldbc-codegen` | ✅ | ✅ | ✅ | +| `ldbc-hikari` | ✅ | ❌ | ❌ | +| `ldbc-plugin` | ✅ | ❌ | ❌ | +| `ldbc-connector` | ✅ | ✅ | ✅ | + ## Documentation - [English](/ldbc/en/index.html) diff --git a/docs/src/main/mdoc/ja/09-Connector.md b/docs/src/main/mdoc/ja/09-Connector.md index fb4e86030..9924da531 100644 --- a/docs/src/main/mdoc/ja/09-Connector.md +++ b/docs/src/main/mdoc/ja/09-Connector.md @@ -2,24 +2,27 @@ この章では、LDBC独自のMySQLコネクタを使用したデータベース接続について説明します。 -ScalaでMySQLデータベースへの接続を行うためにはJDBCを使用する必要があります。JDBCはJavaの標準ライブラリであり、Scalaでも使用することができます。 +ScalaでMySQLデータベースへの接続を行うためにはJDBCを使用する必要があります。JDBCはJavaの標準APIであり、Scalaでも使用することができます。 JDBCはJavaで実装が行われているためScalaで使用する場合でもJVM環境でのみ動作することができます。 昨今のScalaを取り巻く環境はJSやNativeなどの環境でも動作できるようプラグインの開発が盛んに行われています。 -ScalaはJavaの資産を使用できるJVMのみで動作する言語から、クロスプラットフォーム環境でも動作できるよう進化を続けています。 +ScalaはJavaの資産を使用できるJVMのみで動作する言語から、マルチプラットフォーム環境でも動作できるよう進化を続けています。 -しかし、JDBCはJavaの標準ライブラリでありScalaのクロスプラットフォーム環境での動作をサポートしていません。 +しかし、JDBCはJavaの標準APIでありScalaのマルチプラットフォーム環境での動作をサポートしていません。 そのため、ScalaでアプリケーションをJS, Nativeなどで動作できるように作成を行ったとしてもJDBCを使用できないため、MySQLなどのデータベースへ接続を行うことができません。 -Typelevel Projectには[Skunk](https://github.com/typelevel/skunk)と呼ばれるPostgreSQL用のScalaライブラリが存在します。 +Typelevel Projectには[Skunk](https://github.com/typelevel/skunk)と呼ばれる[PostgreSQL](https://www.postgresql.org/)用のScalaライブラリが存在します。 このプロジェクトはJDBCを使用しておらず、純粋なScalaのみでPostgreSQLへの接続を実現しています。そのため、Skunkを使用すればJVM, JS, Native環境を問わずPostgreSQLへの接続を行うことができます。 LDBC コネクタはこのSkunkに影響を受けてJVM, JS, Native環境を問わずMySQLへの接続を行えるようにするために開発が行われてるプロジェクトです。 -※ このコネクタは現在実験的な機能となります。そのため本番環境での使用には注意が必要です。 +※ このコネクタは現在実験的な機能となります。そのため本番環境での使用しないでください。 -プロジェクトに以下の依存関係を設定する必要があります。 +LDBCコネクタは一番低レイヤーのAPIとなります。 +今後このコネクタを使用してより高レイヤーのAPIを提供する予定です。また既存の高レイヤーのAPIとの互換性を持たせることも予定しています。 + +使用するにはプロジェクトに以下の依存関係を設定する必要があります。 **JVM** @@ -37,15 +40,65 @@ libraryDependencies += "$org$" %%% "ldbc-connector" % "$version$" ``` @@@ -コネクタは以下項目に分けて説明を行います。 +**サポートバージョン** -- 認証 -- 実行 -- コネクションプーリング +現在のバージョンは以下のバージョンのMySQLをサポートしています。 + +- MySQL 5.7.x +- MySQL 8.x + +メインサポートはMySQL 8.xです。MySQL 5.7.xはサブサポートとなります。そのためMySQL 5.7.xでの動作には注意が必要です。 +将来的にはMySQL 5.7.xのサポートは終了する予定です。 + +## 接続 + +LDBCコネクタを使用してMySQLへの接続を行うためには、`Connection`を使用します。 + +また、`Connection`はオブザーバビリティを意識した開発を行えるように`Otel4s`を使用してテレメトリデータを収集できるようにしています。 +そのため、`Connection`を使用する際には`Otel4s`の`Tracer`を設定する必要があります。 + +開発時やトレースを使用したテレメトリデータが不要な場合は`Tracer.noop`を使用することを推奨します。 + +```scala +import cats.effect.IO +import org.typelevel.otel4s.trace.Tracer +import ldbc.connector.Connection + +given Tracer[IO] = Tracer.noop[IO] + +val connection = Connection[IO]( + host = "127.0.0.1", + port = 3306, + user = "root", +) +``` + +以下は`Connection`構築時に設定できるプロパティの一覧です。 -## 認証 +| プロパティ | 型 | 用途 | +|-------------------------|--------------------|--------------------------------------------------------| +| host | String | MySQLサーバーのホストを指定します | +| port | Int | MySQLサーバーのポート番号を指定します | +| user | String | MySQLサーバーへログインを行うユーザー名を指定します | +| password | Option[String] | MySQLサーバーへログインを行うユーザーのパスワードを指定します | +| database | Option[String] | MySQLサーバーへ接続後に使用するデータベース名を指定します | +| debug | Boolean | 処理のログを出力します。デフォルトはfalseです | +| ssl | SSL | MySQLサーバーとの通知んでSSL/TLSを使用するかを指定します。デフォルトはSSL.Noneです | +| socketOptions | List[SocketOption] | TCP/UDP ソケットのソケットオプションを指定します。 | +| readTimeout | Duration | MySQLサーバーへの接続を試みるまでのタイムアウトを指定します。デフォルトはDuration.Infです。 | +| allowPublicKeyRetrieval | Boolean | MySQLサーバーとの認証時にRSA公開鍵を使用するかを指定します。デフォルトはfalseです。 | -MySQLでの認証は、クライアントがMySQLサーバーへ接続するときにLoginRequestというフェーズでユーザ情報を送信します。そして、サーバー側では送られたユーザが`mysql.user`テーブルに存在するか検索を行いどの認証プラグインを使用するか決定します。認証プラグインが決定した後にサーバーはそのプラグインを呼び出してユーザー認証を開始し、その結果をクライアント側に送信します。このようにMySQLでは認証がプラガブル(様々なタイプのプラグインを付け外しできる)になっています。 +`Connection`は`Resource`を使用してリソース管理を行います。そのためコネクション情報を使用する場合は`use`メソッドを使用してリソースの管理を行います。 + +```scala +connection.use { conn => + // コードを記述 +} +``` + +### 認証 + +MySQLでの認証は、クライアントがMySQLサーバーへ接続するときにLoginRequestというフェーズでユーザ情報を送信します。そして、サーバー側では送られたユーザが`mysql.user`テーブルに存在するか検索を行い、どの認証プラグインを使用するかを決定します。認証プラグインが決定した後にサーバーはそのプラグインを呼び出してユーザー認証を開始し、その結果をクライアント側に送信します。このようにMySQLでは認証がプラガブル(様々なタイプのプラグインを付け外しできる)になっています。 MySQLでサポートされている認証プラグインは[公式ページ](https://dev.mysql.com/doc/refman/8.0/ja/authentication-plugins.html)に記載されています。 @@ -62,8 +115,291 @@ LDBCが内部で認証プラグインを判断し、適切な認証プラグイ ## 実行 -Comming soon... +以降の処理では以下テーブルを使用しているものとします。 + +```sql +CREATE TABLE users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + age INT NULL +); +``` + +### Statement + +`Statement`は動的なパラメーターを使用しないSQLを実行するためのAPIです。 + +※ `Statement`は動的なパラメーターを使用しないため、使い方によってはSQLインジェクションのリスクがあります。そのため、動的なパラメーターを使用する場合は`PreparedStatement`を使用することを推奨します。 + +`Connection`の`createStatement`メソッドを使用して`Statement`を構築します。 + +#### 読み取りクエリ + +読み取り専用のSQLを実行する場合は`executeQuery`メソッドを使用します。 + +クエリを実行した結果MySQLサーバーから返される値は`ResultSet`に格納されて戻り値として返却されます。 + +```scala +connection.use { conn => + val statement: Statement[IO] = conn.createStatement("SELECT * FROM users") + val result: IO[ResultSet] = statement.executeQuery() + // ResultSetを使用した処理 +} +``` + +#### 書き込みクエリ + +書き込みを行うSQLを実行する場合は`executeUpdate`メソッドを使用します。 + +クエリを実行した結果MySQLサーバーから返される値は影響を受けた行数が戻り値として返却されます。 + +```scala +connection.use { conn => + val statement: Statement[IO] = conn.createStatement("INSERT INTO users (name, age) VALUES ('Alice', 20)") + val result: IO[Int] = statement.executeUpdate() +} +``` + +#### AUTO_INCREMENTの値を取得 + +`Statement`を使用してクエリ実行後にAUTO_INCREMENTの値を取得する場合は`returningAutoGeneratedKey`メソッドを使用します。 + +クエリを実行した結果MySQLサーバーから返される値はAUTO_INCREMENTに生成された値が戻り値として返却されます。 + +```scala +connection.use { conn => + val statement: Statement[IO] = conn.createStatement("INSERT INTO users (name, age) VALUES ('Alice', 20)") + val result: IO[Int] = statement.returningAutoGeneratedKey() +} +``` + +### Client/Server PreparedStatement + +LDBCでは`PreparedStatement`を`Client PreparedStatement`と`Server PreparedStatement`に分けて提供しています。 + +`Client PreparedStatement`は動的なパラメーターを使用してアプリケーション上でSQLの構築を行い、MySQLサーバーに送信を行うためのAPIです。 +そのためMySQLサーバーへのクエリ送信方法は`Statement`と同じになります。 + +このAPIはJDBCの`PreparedStatement`に相当します。 + +より安全なMySQLサーバー内でクエリを構築するための`PreparedStatement`は`Server PreparedStatement`で提供されますので、そちらを使用してください。 + +`Server PreparedStatement`は実行を行うクエリをMySQLサーバー内で事前に準備を行い、アプリケーション上でパラメーターを設定して実行を行うためのAPIです。 + +`Server PreparedStatement`では実行するクエリの送信とパラメーターの送信が分けて行われるため、クエリの再利用が可能となります。 + +`Server PreparedStatement`を使用する場合事前にクエリをMySQLサーバーで準備します。格納するためにMySQLサーバーはメモリを使用しますが、クエリの再利用が可能となるため、パフォーマンスの向上が期待できます。 + +しかし、事前準備されたクエリは解放されるまでメモリを使用し続けるため、メモリリークのリスクがあります。 + +`Server PreparedStatement`を使用する場合は`close`メソッドを使用して適切にクエリの解放を行う必要があります。 + +#### Client PreparedStatement + +`Connection`の`clientPreparedStatement`メソッドを使用して`Client PreparedStatement`を構築します。 + +```scala +connection.use { conn => + for + statement <- conn.clientPreparedStatement("SELECT * FROM users WHERE id = ?") + ... + yield ... +} +``` + +#### Server PreparedStatement + +`Connection`の`serverPreparedStatement`メソッドを使用して`Server PreparedStatement`を構築します。 + +```scala +connection.use { conn => + for + statement <- conn.serverPreparedStatement("SELECT * FROM users WHERE id = ?") + ... + yield ... +} +``` + +#### 読み取りクエリ + +読み取り専用のSQLを実行する場合は`executeQuery`メソッドを使用します。 + +クエリを実行した結果MySQLサーバーから返される値は`ResultSet`に格納されて戻り値として返却されます。 + +```scala +connection.use { conn => + for + statement <- conn.clientPreparedStatement("SELECT * FROM users WHERE id = ?") // or conn.serverPreparedStatement("SELECT * FROM users WHERE id = ?") + _ <- statement.setLong(1, 1) + result <- statement.executeQuery() + yield + // ResultSetを使用した処理 +} +``` + +動的なパラメーターを使用する場合は`setXXX`メソッドを使用してパラメーターを設定します。 +`setXXX`メソッドは`Option`型を使用することもできます。`None`が渡された場合パラメーターにはNULLがセットされます。 + +`setXXX`メソッドはパラメーターのインデックスとパラメーターの値を指定します。 + +```scala +statement.setLong(1, 1) +``` + +現在のバージョンでは以下のメソッドがサポートされています。 + +| メソッド | 型 | 備考 | +|---------------|-------------------------------------|-----------------------------------| +| setNull | | パラメーターにNULLをセットします | +| setBoolean | Boolean/Option[Boolean] | | +| setByte | Byte/Option[Byte] | | +| setShort | Short/Option[Short] | | +| setInt | Int/Option[Int] | | +| setLong | Long/Option[Long] | | +| setBigInt | BigInt/Option[BigInt] | | +| setFloat | Float/Option[Float] | | +| setDouble | Double/Option[Double] | | +| setBigDecimal | BigDecimal/Option[BigDecimal] | | +| setString | String/Option[String] | | +| setBytes | Array[Byte]/Option[Array[Byte]] | | +| setDate | LocalDate/Option[LocalDate] | `java.sql`ではなく`java.time`を直接扱います。 | +| setTime | LocalTime/Option[LocalTime] | `java.sql`ではなく`java.time`を直接扱います。 | +| setTimestamp | LocalDateTime/Option[LocalDateTime] | `java.sql`ではなく`java.time`を直接扱います。 | +| setYear | Year/Option[Year] | `java.sql`ではなく`java.time`を直接扱います。 | + +#### 書き込みクエリ + +書き込みを行うSQLを実行する場合は`executeUpdate`メソッドを使用します。 + +クエリを実行した結果MySQLサーバーから返される値は影響を受けた行数が戻り値として返却されます。 + +```scala +connection.use { conn => + for + statement <- conn.clientPreparedStatement("INSERT INTO users (name, age) VALUES (?, ?)") // or conn.serverPreparedStatement("INSERT INTO users (name, age) VALUES (?, ?)") + _ <- statement.setString(1, "Alice") + _ <- statement.setInt(2, 20) + result <- statement.executeUpdate() + yield result +} + +``` + +#### AUTO_INCREMENTの値を取得 -## コネクションプーリング +クエリ実行後にAUTO_INCREMENTの値を取得する場合は`returningAutoGeneratedKey`メソッドを使用します。 -Comming soon... +クエリを実行した結果MySQLサーバーから返される値はAUTO_INCREMENTに生成された値が戻り値として返却されます。 + +```scala +connection.use { conn => + for + statement <- conn.clientPreparedStatement("INSERT INTO users (name, age) VALUES (?, ?)") // or conn.serverPreparedStatement("INSERT INTO users (name, age) VALUES (?, ?)") + _ <- statement.setString(1, "Alice") + _ <- statement.setInt(2, 20) + result <- statement.returningAutoGeneratedKey() + yield result +} +``` + +### ResultSet + +`ResultSet`はクエリ実行後にMySQLサーバーから返された値を格納するためのAPIです。 + +SQLを実行して取得したレコードを`ResultSet`から取得するには`decode`メソッドを使用します。 + +`decode`メソッドは`ResultSet`から取得した値をScalaの型に変換して取得するためのAPIです。 + +取得するカラムの数に応じて`*:`演算子を使用して変換する型を指定します。 + +例では、usersテーブルのid, name, ageカラムを取得する場合を示しておりそれぞれのカラムの型を指定しています。 + +```scala +result.decode(bigint *: varchar *: int.opt) +``` + +NULL許容のカラムを取得する場合は`Option`型に変換するために`opt`メソッドを使用します。 +これによりレコードがNULLの場合はNoneとして取得することができます。 + +クエリ実行からレコード取得までの一連の流れは以下のようになります。 + +```scala +connection.use { conn => + for + statement <- conn.clientPreparedStatement("SELECT * FROM users WHERE id = ?") // or conn.serverPreparedStatement("SELECT * FROM users WHERE id = ?") + _ <- statement.setLong(1, 1) + result <- statement.executeQuery() + yield + val decodes: List[(Long, String, Option[Int])] = result.decode(bigint *: varchar *: int.opt) + ... +} +``` + +`ResultSet`から取得するレコードは常に配列になります。 +これはMySQLで実行するクエリの結果が常に複数のレコードを返す可能性があるからです。 + +単一のレコードを取得する場合は`decode`処理後に、`head`や`headOption`メソッドを使用して取得を行なってください。 + +現在のバージョンでは以下のデータ型がサポートされています。 + +| Codec | データ型 | Scala 型 | +|-------------|-------------------|----------------| +| boolean | BOOLEAN | Boolean | +| tinyint | TINYINT | Byte | +| utinyint | unsigned TINYINT | Short | +| smallint | SMALLINT | Short | +| usmallint | unsigned SMALLINT | Int | +| int | INT | Int | +| uint | unsigned INT | Long | +| bigint | BIGINT | Long | +| ubigint | unsigned BIGINT | BigInt | +| float | FLOAT | Float | +| double | DOUBLE | Double | +| decimal | DECIMAL | BigDecimal | +| char | CHAR | String | +| varchar | VARCHAR | String | +| binary | BINARY | Array[Byte] | +| varbinary | VARBINARY | String | +| tinyblob | TINYBLOB | String | +| blob | BLOB | String | +| mediumblob | MEDIUMBLOB | String | +| longblob | LONGBLOB | String | +| tinytext | TINYTEXT | String | +| text | TEXT | String | +| mediumtext | MEDIUMTEXT | String | +| longtext | LONGTEXT | String | +| enum | ENUM | String | +| set | SET | List[String] | +| json | JSON | String | +| date | DATE | LocalDate | +| time | TIME | LocalTime | +| timetz | TIME | OffsetTime | +| datetime | DATETIME | LocalDateTime | +| timestamp | TIMESTAMP | LocalDateTime | +| timestamptz | TIMESTAMP | OffsetDateTime | +| year | YEAR | Year | + +※ 現在MySQLのデータ型を指定して値を取得するような作りとなっていますが、将来的にはより簡潔にScalaの型を指定して値を取得するような作りに変更する可能性があります。 + +以下サポートされていないデータ型があります。 + +- GEOMETRY +- POINT +- LINESTRING +- POLYGON +- MULTIPOINT +- MULTILINESTRING +- MULTIPOLYGON +- GEOMETRYCOLLECTION + +## 未対応機能 + +LDBCコネクタは現在実験的な機能となります。そのため、以下の機能はサポートされていません。 +機能提供は順次行っていく予定です。 + +- コネクションプーリング +- トランザクション +- バッチ処理 +- セーブポイント +- フェイルオーバー対策 +- etc...