汚染されたSQLを浄化せよ:FSOと正規表現による極限のSQLクレンジング・アーキテクチャ
レガシーな基幹システムや、長年継ぎ足されてきた社内バッチの現場において、外部ファイルから読み込まれるSQL文は、往々にして「汚染」されている。
開発者が残したデバッグ用のコメント、冗長な改行、そして解析エンジンを混乱させる不規則な空白。これらは、実行時エラーを引き起こす直接的な原因にはならずとも、ログの視認性を下げ、デバッグのコストを増大させ、最悪の場合は動的SQLの構築時に予期せぬ構文エラーを誘発する。
本稿では、単なる「テキストの読み込み」という初歩的な次元を超え、`FileSystemObject` (FSO) を核とした、堅牢かつ高効率なSQLクレンジング・エンジンの設計思想を伝授する。シニアエンジニアとして、メモリ管理とオブジェクトのライフサイクルを掌握した実装を身につけてほしい。
1. 凡庸な実装が陥る「状態管理」の罠
多くの開発者は、SQLのコメント除去を「行単位のループ処理」で解決しようとする。
' --- 避けるべき、凡庸な実装のイメージ ---
Do Until objTextStream.AtEndOfStream
strLine = objTextStream.ReadLine
If InStr(strLine, "--") = 0 Then ' これでは /* ... */ の複数行コメントに対応できない
' ... 処理 ...
End If
Loop
このアプローチには致命的な欠陥がある。SQLにおけるコメントには、行コメント(`–`)だけでなく、複数行にわたるブロックコメント(`/* … */`)が存在するからだ。行単位の走査では、「コメントの開始行」と「終了行」の間の状態(State)を管理する複雑なステートマシンを自前で実装せねばならず、コードの複雑度は指数関数的に増大する。
我々プロフェッショナルが選択すべきは、「ファイル全体をバッファに展開し、正規表現(RegExp)による一括置換を行う」という戦略である。これにより、ステート管理の複雑さを正規表現エンジンへと委譲し、コードの純粋性を保つ。
2. 極限の実装:SQLクレンジング・エンジン
以下に、実務でそのまま通用するレベルの、堅牢なクレンジング・スクリプトを示す。ここでは、メモリ効率を考慮し、オブジェクトの明示的な解放を徹底している。
'***
' Module: SQLCleanser
' Purpose: FSO and RegExp to sanitize SQL files (Remove comments and excess whitespace)
' Author: Chief Architect
'***
Option Explicit
' メイン実行ルーチン
Sub Main()
Dim strInputFile, strOutputFile
strInputFile = "C:\Temp\input_query.sql"
strOutputFile = "C:\Temp\output_query_clean.sql"
On Error Resume Next
Dim strCleanedSQL
strCleanedSQL = SanitizeSQLFile(strInputFile)
If Err.Number <> 0 Then
WScript.Echo "CRITICAL ERROR: " & Err.Description
WScript.Quit 1
End If
On Error GoTo 0
' 結果の書き出し
If SanitizeWrite(strOutputFile, strCleanedSQL) Then
WScript.Echo "SUCCESS: SQL has been purified and saved to " & strOutputFile
Else
WScript.Echo "ERROR: Failed to write the purified SQL."
End If
End Sub
'***
' Function: SanitizeSQLFile
' Description: Reads file, applies Regex patterns to strip comments and whitespace.
'***
Function SanitizeSQLFile(strFilePath)
Dim objFSO, objFile, strContent
Dim objRegEx, strPatternBlock, strPatternLine, strPatternSpace
' オブジェクト生成
Set objFSO = CreateObject("Scripting.FileSystemObject")
' ファイルの存在確認(ガード句)
If Not objFSO.FileExists(strFilePath) Then
Err.Raise 1, "SanitizeSQLFile", "Input file not found: " & strFilePath
End If
' 1. ファイルの全内容をメモリにロード
' ※極端に巨大なファイル(数GB)の場合はストリーム処理が必要だが、
' SQLクエリであればメモリバッファ方式が最速である。
Set objFile = objFSO.OpenTextFile(strFilePath, 1) ' 1 = ForReading
strContent = objFile.ReadAll
objFile.Close
Set objFile = Nothing ' 明示的な解放
' 2. 正規表現エンジンの初期化
Set objRegEx = CreateObject("VBScript.RegExp")
objRegEx.Global = True
objRegEx.IgnoreCase = True
objRegEx.MultiLine = True
' A. ブロックコメントの除去: /* ... */ (非強欲マッチを利用)
' [\s\S] は改行を含むすべての文字にマッチさせるテクニック
strPatternBlock = "/\*[\s\S]*?\*/"
objRegEx.Pattern = strPatternBlock
strContent = objRegEx.Replace(strContent, "")
' B. 行コメントの除去: -- ...
strPatternLine = "--.*$"
objRegEx.Pattern = strPatternLine
strContent = objRegEx.Replace(strContent, "")
' C. 不要な空白・改行の集約
' 連続する空白、タブ、改行を単一のスペースに置換
strPatternSpace = "\s+"
objRegEx.Pattern = strPatternSpace
strContent = objRegEx.Replace(strContent, " ")
' 3. 後処理:前後の余分な空白をトリム
SanitizeSQLFile = Trim(strContent)
' オブジェクトのライフサイクル管理(メモリリーク防止)
Set objRegEx = Nothing
Set objFSO = Nothing
End Function
'***
' Function: SanitizeWrite
' Description: Writes the sanitized string to a file.
'***
Function SanitizeWrite(strFilePath, strText)
Dim objFSO, objFile
On Error Resume Next
Set objFSO = CreateObject("Scripting.FileSystemObject")
' 2 = ForWriting, True = Create if not exists
Set objFile = objFSO.CreateTextFile(strFilePath, True)
If Err.Number = 0 Then
objFile.Write strText
objFile.Close
SanitizeWrite = True
Else
SanitizeWrite = False
End If
Set objFile = Nothing
Set objFSO = Nothing
On Error GoTo 0
End Function
' 実行
Main
3. アーキテクトの視点:極限の知見
このコードを単なる「動くスクリプト」として終わらせてはならない。現場で運用する際には、以下の3点に留意せよ。
① オブジェクトのライフサイクルとメモリ管理
VBScriptにおける `Set obj = Nothing` は、単なるマナーではない。特に、ループ内で `FileSystemObject` や `RegExp` を生成するような設計にした場合、明示的に解放しなければ、COMオブジェクトの参照カウントが減らず、メモリリークを引き起こす。長時間稼働するバッチプロセスにおいて、これは最終的に `Out of Memory` によるシステムの沈黙を招く。
② 正規表現の「非強欲マッチ(Non-greedy Match)」
ブロックコメントの除去において、`/\*[\s\S]*\*/` というパターンは致命的なミスを生む。これは「強欲(Greedy)マッチ」と呼ばれ、ファイル内に複数のコメントがある場合、最初に出現する `/*` から最後に出現する `*/` までを一つの巨大なコメントとして誤認して削除してしまう。
必ず `*?` を用い、最小単位でのマッチング(非強欲マッチ)を徹底すること。
③ エンコーディングの境界線(FSOの限界)
ここが最も重要なプロフェッショナルの境界線だ。
`FileSystemObject` は、主に ANSI または UTF-16 (Unicode) を扱う。もし、読み込むSQLファイルが UTF-8 (BOMなし) である場合、FSOは文字化けを引き起こし、クレンジング処理は完全に失敗する。
もし、現代的な UTF-8 環境との連携が必須であれば、FSOを捨て、`ADODB.Stream` オブジェクトを使用せよ。`ADODB.Stream` であれば、`Charset = “utf-8″` を指定することで、エンコーディングの呪縛から解放される。
結論
SQLのクレンジングは、一見すると些細な作業に見える。しかし、入力データの「純度」を制御することは、システム全体の信頼性を担保するための、エンジニアの責務である。
FSOによるファイル操作、RegExpによる高度なパターンマッチング、そしてオブジェクトの適切な解放。これらを組み合わせて初めて、堅牢な自動化基盤が完成する。
「コードは書かれた後、それが動いている間中、メモリの中で生き続けている」
この事実を忘れないことだ。
