はじめに
Pleasanterは、SQL Server、PostgreSQL、MySQLという3つの異なるRDBMSに対応しています。それぞれのRDBMSは独自の方言を持っていますが、CodeDefinerはこれらの違いを吸収しながら自動的にテーブルを作成する仕組みを持っています。
本記事では、CodeDefinerがどのようにテーブル作成SQLを生成し、RDBMS間の差異を吸収しているのか、その仕組みを見ていきます。
テーブル作成テンプレートの基本構造
テンプレートファイルの役割
CodeDefinerは、Implem.Pleasanter/App_Data/Definitions/Definition_Sql/CreateTable_Body.txtというテンプレートファイルを使用してテーブル作成SQLを生成します。
このテンプレートには、# で囲まれたプレースホルダーが含まれています。CodeDefinerは、実行時にこれらを実際の値に置き換えてSQLを生成します。
プレースホルダーの詳細
テンプレート内のプレースホルダーは、以下のように置き換えられます。
| プレースホルダー | 置き換えられる内容 | 具体例 |
|---|---|---|
#TableName# |
作成するテーブル名 |
Users, Issues, Results
|
#DropConstraint# |
既存制約の削除SQL | 主に更新時に、既存の制約を削除するSQL |
#Columns# |
カラム定義のリスト |
"UserId" bigint identity(1,1) not null,"LoginId" nvarchar(100) not null
|
#Pks# |
主キー制約の定義 | CONSTRAINT "PK_Users" PRIMARY KEY ("UserId") |
#Defaults# |
デフォルト値制約 | ALTER TABLE ... ADD DEFAULT ... |
プレースホルダー置換の実装
プレースホルダーの置換は、Tables.CreateTable() メソッドで行われます。
それぞれ次のように実装されています。
// テンプレートを読み込み
var sqlStatement = new SqlStatement( Def.Sql.CreateTable, Sqls.SqlParamCollection());
// 各プレースホルダーに対応する内容を生成
sqlStatement.CreateColumn(factory, sourceTableName, columnDefinitionCollection); sqlStatement.CreatePk(sourceTableName, columnDefinitionCollection, tableIndexCollection); sqlStatement.CreateIx(factory: factory, generalTableName: generalTableName, ...); sqlStatement.CreateDefault(factory, tableNameTemp, columnDefinitionCollection); sqlStatement.DropConstraint(factory: factory, sourceTableName: sourceTableName, ...);
// テーブル名を置換
qlStatement.CommandText = sqlStatement.CommandText.Replace("#TableName#", tableNameTemp);
// SQLを実行
Def.SqlIoByAdmin(factory: factory, transactional: true).ExecuteNonQuery(...);
RDBMS間の差異を吸収する仕組み
ファクトリパターンによる実装の切り替え
CodeDefinerは、ファクトリパターンとISqlsインターフェースを使用して、RDBMS固有の処理を切り替えています。
ここで呼び出されたファクトリ内部にはそれぞれのRDBMS固有のコマンドを保持している部分があります。代表的なものは下記のものです。
// RDBMS別の現在日時関数
factory.Sqls.CurrentDateTime
// RDBMS別の自動連番定義
factory.Sqls.GenerateIdentity
主要な差異の例
以下では、RDBMS間で大きく異なる部分について、具体的にどのように対応しているかを見ていきます。
1. IDENTITY(自動連番)の違い
自動連番カラムの定義方法は、RDBMSごとに大きく異なります。
// SQL Server
public string GenerateIdentity { get; } = " generated by default as identity (start with {0} increment by 1)";
// PostgreSQL
public string GenerateIdentity { get; } = " generated by default as identity (start with {0} increment by 1)";
// MySQL
public string GenerateIdentity { get; } = string.Empty;
MySQLの場合、主キー制約追加後に別途auto_incrementを設定して対応しています。(ここでは割愛します)
2. 現在日時関数の違い
デフォルト値として現在日時を設定する場合の関数名が異なります。
| RDBMS | 関数 |
|---|---|
| SQL Server | getdate() |
| PostgreSQL | CURRENT_TIMESTAMP |
| MySQL | CURRENT_TIMESTAMP(3) |
// SQL Server
public string CurrentDateTime { get; } = " getdate() ";
// PostgreSQL
public string CurrentDateTime { get; } = " CURRENT_TIMESTAMP ";
// MySQL
public string CurrentDateTime { get; } = " CURRENT_TIMESTAMP(3) ";
3. NULL値処理関数の違い
NULL値を別の値に置き換える関数が異なります。
| RDBMS | 関数 |
|---|---|
| SQL Server | isnull(column, value) |
| PostgreSQL | coalesce(column, value) |
| MySQL | ifnull(column, value) |
// SQL Server
public string IsNull { get; } = "isnull";
// PostgreSQL
public string IsNull { get; } = "coalesce";
// MySQL
public string IsNull { get; } = "ifnull";
4. データ型の自動変換
RDBMSごとに対応しているデータ型が異なるため、CodeDefinerは自動的にデータ型を変換します。Pleasanterではベースの実装はSQL Serverとなっているため、PosrgreSQL、MySQLに対してのみ変換を行っています。
PostgreSQL の場合
| 元のデータ型 | 変換後 |
|---|---|
nvarchar |
varchar |
datetime |
timestamp |
bit |
boolean |
varbinary(max) |
bytea |
PostgreSqlDataTypes.Convert() メソッドが、これらの変換を自動的に実施します。
MySQL の場合
| 元のデータ型 | 変換後 |
|---|---|
nvarchar |
varchar または text
|
datetime |
datetime(3) |
bit |
tinyint(1) |
varbinary(max) |
blob |
varchar(1024) のような大きなサイズを多用するとテーブル作成時にエラーが発生するという制約があります。そのため、下記の条件に従って自動的にデータ型を調整します。
| 条件 | 変換後のデータ型 | 理由 |
|---|---|---|
| デフォルト値あり | varchar(200) |
text型にはデフォルト値を設定できない |
| インデックス設定あり | varchar(200) |
text型のインデックスにはサイズ制限がある |
| 上記以外 | text |
より大きなデータを格納できる |
テーブル作成の全体的な流れ
実際にテーブルが作成されるまでの処理の流れをまとめるとこのようになります。
- CodeDefiner 起動
- RDBMS判定(SQL Server/PostgreSQL/MySQL)
- ファクトリ生成
var factory = RdsFactory.Create(Parameters.Rds.Dbms) - テーブル定義読み込み
ColumnDefinitionCollectionから対象テーブルの定義を取得 - テンプレート読み込み
Def.Sql.CreateTable(CreateTable_Body.txtの内容) - プレースホルダー置換
6.CreateColumn()→#Columns#
6.CreatePk()→#Pks#
6.CreateIx()→インデックス定義
6.CreateDefault()→#Defaults#
6.DropConstraint()→#DropConstraint#
6.Replace("#TableName#", tableName) - データ型変換
factory.SqlDataType.Convert()で RDBMS固有のデータ型に変換 - SQL実行
Def.SqlIoByAdmin().ExecuteNonQuery() - テーブル作成完了
まとめ
PleasanterのCodeDefinerは、以下の仕組みでマルチRDBMS対応を実現しています。
- テンプレートベースのSQL生成
- プレースホルダー方式により、柔軟なSQL生成が可能
- RDBMS共通の構造を維持しながら、固有の部分を差し替え
- ファクトリパターンによる実装の切り替え
-
ISqlsインターフェースでRDBMS固有の処理を定義 - 実行時にRDBMSに応じた実装クラスを選択
-
- 自動データ型変換
- RDBMS固有のデータ型に自動変換
-
SqlDataType.Convert()メソッドで一括変換
- 制約の自動調整
- MySQLの
varcharサイズ制限などに自動対応 - デフォルト値やインデックスの有無に応じた最適な型を選択
- MySQLの
これらの仕組みにより、開発者は単一のテーブル定義を記述するだけで、3つのRDBMSすべてに対応したテーブルを自動生成できるようになっています。