ささいなことですが。

Windowsアプリテスト自動化ライブラリFriendly開発者の日記です。

最近のLambdicSql - この世の全てのSQLをC#で表現する

github.com
まだβ版です。とはいえ、今度こそ着実にリリースに近づいています。
この辺で最近入れた機能をご紹介させていただきます。
それは句や関数を簡単に追加できる機能です。

この世の全てのSQLC#で表現するポテンシャルを持たせました。

若干言い過ぎ感はありますがw
LambdicSqlは多くの句や関数がデフォルトで組み込まれています。今のところの判断基準は私が読んだSQLの入門書に書かれていたものです。(えっ?)でも、もちろんこれでは足りず、かといって全網羅するのは大変だしなーってことで、ユーザー側で簡単に追加できるようにしました。
LambdicSql自体も、ここで解説している仕組みを使って関数や句を定義しているので、それがサンプルになると思います。よろしければ参照してください。

一般的な関数の追加

FuncStyleConverterAttributeをつけるだけで使えるようになります。
以下のルールで変換できます。

  • 関数名はC#のものが大文字にって使われる(Nameプロパティで別途指定も可能)
  • 引数に括弧がつく
  • 間がカンマで区切られる
//使いたい関数を定義
static class MyFuncs
{
    [FuncStyleConverter]
    internal static Cos(double angle) { throw new NotSupportedException(); }
}

それで定義の中身なのですが、実行されることはないので空っぽでOKです。(Exceptionの解析に使われるだけです)
using static MyFuncs;
を書いておくとよりSQLっぽさがでます。

//使える!
void Test()
{
    var sql = Db<DB>.Sql(db =>
        Select(new SelectedData
        {
            Val = Cos(db.tbl1.angle),
        }).
        From(db.tbl1)                
    );

    Console.WriteLine(sql.Build(_connection.GetType()).Text);
}

こんな感じで変換されます。

SELECT
        COS(tbl1.angle) AS Val
FROM tbl1

一般的な句の追加

ClauseStyleConverterAttributeを使います

  • 句名はC#のものが大文字にって使われる(Nameプロパティで別途指定も可能)
  • 句と第一引数の間はスペースで区切られる
  • 間がカンマで区切られる

句は結構実装したので良いのが思いつきませんでした。
それでLambdicSqlで定義してますがサンプルはLIMIT句を使います。

//使いたい句を定義 
static class MyFuncs
{
    [ClauseStyleConverter]
    public static ClauseChain<Non> Limit(object offset, object count) { throw new InvalitContextException(nameof(Limit)); }

    [ClauseStyleConverter]
    public static ClauseChain<TSelected> Limit<TSelected>(this ClauseChain<TSelected> before, object offset, object count) { throw new InvalitContextException(nameof(Limit)); }
}

句はどうしてもメソッドチェーンでつなげたいですよね。なのでClauseChainというクラスの拡張メソッドとして定義してもらうとOKです。それでバラバラに書いて後でくっつけたい場合もあるので拡張ではないバージョンも定義しておくと便利です。

void Test()
{
    var sql = Db<DB>.Sql(db =>
            Select(Asterisk(db.tbl_remuneration)).
            From(db.tbl_remuneration).
            OrderBy(Asc(db.tbl_remuneration.id)).
            Limit(1, 3)
            );

    Console.WriteLine(sql.Build(_connection.GetType()).Text);
}
SELECT *
FROM tbl_remuneration
ORDER BY
	tbl_remuneration.id ASC
LIMIT @p_0, @p_1

フォーマットを自分で指定するもの

とは言え、別のフォーマットのものもありますよ。ということでフォーマットを指定したい場合はMethodFormatConverterAttributeを使います。ついでにenumを使いたい場合の説明も。
TABLESAMPLEでやってみます。

static class MyFuncs
{
    [MethodFormatConverter(Format = "TABLESAMPLE [0](|[1])")]
    public static ClauseChain<Non> TableSample(SamplingMethod method, double percentage) { throw new InvalitContextException(nameof(Limit)); }

    [MethodFormatConverter(Format = "TABLESAMPLE [1](|[2])")]
    public static ClauseChain<TSelected> TableSample<TSelected>(this ClauseChain<TSelected> before, SamplingMethod method, double percentage) { throw new InvalitContextException(nameof(Limit)); }
}

[EnumToStringConverter]
public enum SamplingMethod
{
    //大文字になる
    System,

    //別途文字列指定もできる
    [FieldSqlName("BERNOULLI")]
    Bernoulli
}
void Test()
{
    var sql = Db<DB>.Sql(db =>
            Select(Asterisk(db.tbl_remuneration)).
            From(db.tbl_remuneration).
            OrderBy(Asc(db.tbl_remuneration.id)).
            Limit(1, 3)
            );

    Console.WriteLine(sql.Build(_connection.GetType()).Text);
}
SELECT *
FROM tbl_remuneration
TABLESAMPLE SYSTEM(@p_0)

Formatで指定した感じに文字列化されます。登場する記号類です。

記号 内容
| 改行が入る場合でも、その位置までは一行目に残す
[i] i番目の引数を入れる
[<, >i] i番目の引数を展開する。その引数は配列である必要がある。<>の中はセパレータを指定する。
[$i] パラメータを使わず直値でSQLに文字列として入れる
[#i] カラムが入った場合テーブル名なしでカラム名のみにする
[!i] 特殊文字列。指定された文字列を直接SQLに入れる。db名や制約名称に利用。

さらに特殊な場合

MethodConverterAttributeを継承します。実は他にもnew、フィールド、プロパティ、オブジェクトを変換することもできます。

MethodConverterAttribute メソッド
NewConverterAttribute コンストラクタ
ObjectConverterAttribute オブジェクトの変換。型に使う
MemberConverterAttribute フィールド、プロパティー

Expressionがわたってくるのでそれを解析して、コードにして返します。
LambdicSqlでもいくつかはこれを使っているのでサンプルとしてはこちらを参照お願いします。

public abstract ICode Convert(MethodCallExpression expression, ExpressionConverter converter);

仲間を募集中

ユーザー定義で増やせるとは言え、デフォルトで入ってた方が良いですよねー。なのでもっとLambdicSqlで定義されている分を増やしたい。今は一つのクラスに定義してるけど、DB別とかで定義したら、そのDBで使えるものだけインテリセンスにでて便利度UPですよね。でも一人では工数が足りない・・・。だれか一緒に、すべてのSQLをC#で表現しませんか?仲間募集中です!