読者です 読者をやめる 読者になる 読者になる

ScriptDom でクエリを改変する

.NET SQL Server

ScriptDom でパースした TSqlFragment をちょっといじって、ジェネレーターで生成するクエリを改造しようという試みです。
TSqlFragment を全部コードで生成するのは結構面倒ですが、パースした後の物を変更する位なら使えるかなと思います。


今回は、複数の CREATE TABLE のスクリプトを一つのスクリプトにまとめつつ、どのテーブルにも絶対必要な共通的なカラム*1を追加してみます。
ER 図には書いてないけど DDL(CREATE TABLE) では必要なケースでは使いどころがあるかもしれませんね。


ここでは、更新者、更新日、VERSION_NO という3つのカラムを追加します。

Visitor

public class AddColumnVisitor : TSqlConcreteFragmentVisitor {
  /// <summary>
  /// CREATE TABLE を バッチ に束ねた Script
  /// </summary>
  public TSqlScript Script { get; private set; }
  public AddColumnVisitor() {
    Script = new TSqlScript();
  }
  public override void Visit(CreateTableStatement node) {
    // Script に 今回の CreateTable をバッチとして追加する
    var batch = new TSqlBatch();
    batch.Statements.Add(node);
    Script.Batches.Add(batch);

    // [更新者] nvarchar(10)
    var type = new SqlDataTypeReference() {
      SqlDataTypeOption = SqlDataTypeOption.NVarChar
    };
    type.Parameters.Add(new IntegerLiteral() { Value = "10" });
    node.Definition.ColumnDefinitions.Add(new ColumnDefinition() {
      ColumnIdentifier = new Identifier() {
        QuoteType = QuoteType.SquareBracket, 
        Value = "更新者"
      }, 
      DataType = type
    });
    // [更新日] datetime2
    node.Definition.ColumnDefinitions.Add(new ColumnDefinition() {
      ColumnIdentifier = new Identifier() {
        QuoteType = QuoteType.SquareBracket,
        Value = "更新日"
      },
      DataType = new SqlDataTypeReference() {
        SqlDataTypeOption = SqlDataTypeOption.DateTime2
      }
    });
    // [VERSION_NO] bigint
    node.Definition.ColumnDefinitions.Add(new ColumnDefinition() { 
      ColumnIdentifier = new Identifier() { 
        QuoteType = QuoteType.SquareBracket, 
        Value = "VERSION_NO"
      },
      DataType = new SqlDataTypeReference() { 
        SqlDataTypeOption = SqlDataTypeOption.BigInt
      }
    });
    base.Visit(node);
  }
}

CreateTableStatement.Definition プロパティ (Microsoft.SqlServer.TransactSql.ScriptDom)TableDefinition.ColumnDefinitions プロパティ (Microsoft.SqlServer.TransactSql.ScriptDom) にカラムの定義を追加していきます。


CREAET TABLE のスクリプトは 3ファイル用意しました。

.ddl/社員マスタ.sql

create table [社員マスタ] ( 
  [Id] int not null identity(1, 1) primary key, 
  [名前] nvarchar(20) not null, 
  [入社日] date
)

.ddl/部門マスタ.sql

create table [部門マスタ] ( 
  [Id] int not null identity(1, 1) primary key, 
  [名前] nvarchar(20) not null
)

.ddl/所属マスタ.sql

create table [所属マスタ] ( 
  [Id] int not null identity(1, 1) primary key, 
  [社員Id] int not null, 
  [部門Id] int not null 
)

本体のコードは大したことしてません。パース/Visitor/生成 してるだけです。

using Microsoft.SqlServer.TransactSql.ScriptDom;
using System;
using System.Collections.Generic;
using System.IO;

class Program {
  static void Main(string[] args) {
    var parser = new TSql110Parser(false);
    IList<ParseError> errors;
    TSqlFragment f;
    var visitor = new AddColumnVisitor();
    foreach (var file in Directory.GetFiles(@".\ddl", "*.sql")) {
      using (var reader = File.OpenText(file)) {
        f = parser.Parse(reader, out errors);
      }
      // サンプルなのでエラー処理しない
      f.Accept(visitor);
    }
    var options = new SqlScriptGeneratorOptions() {
      IndentationSize = 2, 
      KeywordCasing = KeywordCasing.Lowercase
    };
    var generator = new Sql110ScriptGenerator(options);
    string output; // 出力は TextWrite でもOK!
    generator.GenerateScript(visitor.Script, out output);
    Console.WriteLine(output);
    Console.ReadKey();
  }
}


出来ましたね!ただ、残念ながらカラムの型の部分をそろえる GeneratorOption は無さそうです。。

*1:排他制御用のVERSION_NOだとか