Access VBAの暗部を制御せよ:QueryDef実行を捕捉する「監査フック」の極意
Accessにおける`QueryDef`は、単なるSQLの格納庫ではない。それはデータベースエンジン(ACE/Jet)との対話の最前線であり、同時にガバナンスの死角となり得る。
多くの開発者は、`DoCmd.OpenQuery`や`db.Execute`を無造作に呼び出し、誰が、いつ、何をデータベースに書き込んだのかという「足跡」をブラックボックス化させている。これはプロフェッショナルなシステム開発の現場では許容されない怠慢だ。
今日は、QueryDefに公式な「実行前イベント」が存在しないというAccessの仕様上の欠陥を、エンジニアリングの力でねじ伏せる。SQL実行を完全に掌握し、監査ログを自動生成する「擬似フック」の実装手法を授ける。
—
1. 監査フックの設計思想:プロキシパターンの適用
直接`QueryDef`を操作させるのではなく、必ず「ラッパー関数」を経由させるアーキテクチャを採用する。
- フックポイント: `Execute`メソッド呼び出しの直前。
- メタデータ取得: `CurrentProject`からユーザー情報、`Environ`関数、必要であれば`GetUserName` APIによるOSレベルのID取得。
- 整合性: トランザクションとログの原子性を考慮し、エラーハンドリングを強固にする。
2. 実装:Windows APIを活用した精緻なログ記録
まずは、システム上の「真の実行者」を特定するために、Windows APIを呼び出す。環境変数`USERNAME`は容易に改ざんできるが、API経由で取得するセキュリティトークンは信頼の基盤となる。
‘ 標準モジュール: modAuditEngine
Option Compare Database
Option Explicit
‘ Windows API: プロセス実行ユーザーの取得
Private Declare PtrSafe Function GetUserName Lib “advapi32.dll” Alias “GetUserNameA” ( _
ByVal lpBuffer As String, _
nSize As Long) As Long
‘ 実行ログを記録するプロキシ関数
Public Sub ExecuteQuerySafely(queryName As String, Optional options As Long = dbFailOnError)
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim userName As String
Set db = CurrentDb
Set qdf = db.QueryDefs(queryName)
‘ 実行前監査ログの記録
userName = GetWindowsUser()
WriteAuditLog userName, qdf.Name, qdf.SQL
‘ 本番実行
On Error GoTo ErrHandler
qdf.Execute options
‘ オブジェクトの明示的解放(メモリリークの温床を断つ)
Set qdf = Nothing
Set db = Nothing
Exit Sub
ErrHandler:
‘ 異常終了ログを記録し、例外を再送出
LogError Err.Number, Err.Description, queryName
Set qdf = Nothing
Set db = Nothing
Err.Raise Err.Number, “ExecuteQuerySafely”, Err.Description
End Sub
Private Function GetWindowsUser() As String
Dim buffer As String 255
Dim length As Long
length = 255
If GetUserName(buffer, length) > 0 Then
GetWindowsUser = Left$(buffer, length – 1)
Else
GetWindowsUser = “Unknown”
End If
End Function
—
3. パラメータークエリへの対応:SQLインジェクションの防壁
パラメータークエリを動的に生成する際、連結(Concatenation)でSQLを組み立てるのは素人の所業だ。必ず`Parameters`コレクションを使用せよ。
‘ パラメータークエリを動的に実行する際のベストプラクティス
Public Sub ExecuteParametricQuery(queryName As String, ParamArray params() As Variant)
Dim qdf As DAO.QueryDef
Dim i As Integer
Set qdf = CurrentDb.QueryDefs(queryName)
‘ パラメーターのバインド
For i = LBound(params) To UBound(params)
qdf.Parameters(i).Value = params(i)
Next i
‘ 監査ログ記録後に実行
‘ (先述のExecuteQuerySafelyを再利用するか、ここでログ記録を呼び出す)
qdf.Execute dbFailOnError
Set qdf = Nothing
End Sub
—
4. チーフアーキテクトからの忠告
オブジェクトのライフサイクル管理
Access VBAにおいて、`Set qdf = Nothing`をサボることは、特に長期間起動し続ける業務アプリにおいて、メモリリークという名の「時限爆弾」を仕掛けることに等しい。`DAO`オブジェクトは必ずスコープの最後で明示的に解放せよ。
パフォーマンスの重み
ログ記録はデータベースのI/Oを伴う。もし監査ログテーブルへの書き込みがボトルネックになる場合は、`DAO.Recordset`でログテーブルを開き、`AddNew`で追記する手法をとるか、あるいは`ADO`の非同期実行を検討すべきだ。だが、まずは「同期処理で確実に記録する」ことがガバナンス上の正解である。
レガシー環境での保守性
このコードを導入する際、既存のクエリをすべて書き換えるのは骨が折れるだろう。その場合は、`VBA.Collection`を用いた実行ハンドラーを構築し、システム全体の実行窓口を一本化するリファクタリングを推奨する。
システムとは、作って終わりではない。「誰がいつ何をしたか」を語れる状態にして初めて、それはプロフェッショナルの道具となる。 諸君のシステムが、堅牢な監査のログに守られることを期待している。
