【テクニカル・上級編】【実務】隠しテーブルやシステムテーブルをVBAで安全に操作する注意点 – Access VBA解析バイブル

スポンサーリンク

【実務】隠しテーブルやシステムテーブルをVBAで安全に操作する極限の知見

レガシーシステムの最前線でAccessと伴走し続けるエンジニアにとって、`MSysObjects`をはじめとするシステムテーブルや、UI上で不可視化された「隠しテーブル」の制御は、避けて通れない聖域である。

一般の参考書では「システムテーブルは触るな」「触るとデータベースが壊れる」の一言で片付けられる。だが、シニアエンジニアやシステム管理者にとって、マイグレーション時の依存関係解析、動的なオブジェクト存在確認、あるいは肥大化したデータベースのメタデータ監査において、これらのテーブルを安全に読み解くスキルは、プロフェッショナルの必須要件なのだ。

本稿では、Access VBAを用いてシステムテーブルおよび隠しテーブルへ安全にアクセスし、メモリリークを完全に排除した極限のコードパターンを解説する。

1. システムテーブル・隠しテーブルのアーキテクチャとリスク

Access(Jet/ACEエンジン)の内部において、すべてのテーブル、クエリ、フォーム、レポート、マクロの定義は `MSysObjects` というシステムテーブルにメタデータとして集約されている。

これらを直接操作する際、以下の重大なリスクが存在する。

  • 排他制御とロック: システムテーブルに対して不適切なトランザクションや書き込みを行おうとすると、ACEエンジンが即座に整合性エラーを返し、ACCDBファイル全体が破損する。
  • 権限と可視性の罠: デフォルトでは `MSysObjects` への読み取り権限すらない場合があり、クエリからの直接参照には明示的な設定が必要となる。
  • 隠し属性(Hidden Attributes)の誤認: ユーザーが意図的に隠したテーブルと、システムが内部管理しているテーブルは別物である。DAOの `TableDef.Attributes` プロパティを正しく解釈しなければならない。

大原則:システムテーブルへの書き込み(INSERT/UPDATE/DELETE)は絶対に厳禁である。 我々が許されるのは、最大限の安全性に配慮した「読み取り(SELECT)」、および DAO を介した「メタデータの安全な列挙」のみである。

2. 安全なアクセスを実現するDAOと隠し属性の判定ロジック

ADOを用いたシステムテーブルへのアクセスは、プロバイダの制限により予期せぬエラーを生むことが多い。ACEエンジンのネイティブな挙動を完全に制御するためには、DAO(Data Access Objects)を用いるのが唯一にして最良の選択肢である。

以下のコードは、データベース内に存在するすべてのテーブル(通常のユーザーテーブル、隠しテーブル、システムテーブル)を安全に走査し、その属性を判別する実務用のプロシージャである。

【実務コード】全テーブルの属性判別とメタデータ抽出

Option Compare Database
Option Explicit

‘ ==============================================================================
‘ 処理名 : EnumAllTablesSafely
‘ 概要 : システムテーブルや隠しテーブルを含む全テーブルの属性を安全に取得し、
イミディエイトウィンドウに出力する。
‘ 備考 : オブジェクトの明示的解放(Dispose)を徹底し、メモリリークを防ぐ。
‘ ==============================================================================
Public Sub EnumAllTablesSafely()
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim i As Long

‘ 現在のデータベース参照を取得(CurrentDbの多重呼出しはメモリリークの温床となるため変数化)
Set db = CurrentDb()

On Error GoTo ErrorHandler

Debug.Print “————————————————–”
Debug.Print ” テーブル解析開始 : ” & Now
Debug.Print “————————————————–”

‘ TableDefsコレクションの走査
For i = 0 To db.TableDefs.Count – 1
Set tdf = db.TableDefs(i)

‘ 判定変数の初期化
Dim isSystem As Boolean
Dim isHidden As Boolean

isSystem = False
isHidden = False

‘ 1. システムテーブルの判定 (名前が “MSys” で始まるか)
If Left$(tdf.Name, 4) = “MSys” Then
isSystem = True
End If

‘ 2. リンクテーブル(外部接続)の判定
If Len(tdf.Connect) > 0 Then
‘ リンクテーブル特有の処理をここに記述可能
End If

‘ 3. 隠し属性の判定 (DAO.TableDef.Attributes のビットマスク評価)
‘ dbHiddenObject (1) ビットが立っているかを評価
If (tdf.Attributes And dbHiddenObject) <> 0 Then
isHidden = True
End If

‘ 結果の出力(実務ではここで動的クエリ生成や監査ログへの書き込みを行う)
Debug.Print “Name: ” & tdf.Name & _
” | System: ” & isSystem & _
” | Hidden: ” & isHidden & _
” | Records: ” & GetRecordCountSafely(tdf)

‘ ループ内でのオブジェクト解放
Set tdf = Nothing
Next i

CleanUp:
‘ データベースオブジェクトの確実な解放
If Not db Is Nothing Then
Set db = Nothing
End If
Debug.Print “————————————————–”
Debug.Print ” テーブル解析終了”
Debug.Print “————————————————–”
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”
Resume CleanUp
End Sub

‘ ==============================================================================
‘ 補助関数 : 破損テーブルやシステムテーブルでのレコード数取得時エラー回避
‘ ==============================================================================
Private Function GetRecordCountSafely(ByRef tdf As DAO.TableDef) As Variant
On Error GoTo Catch
‘ 一部のシステムテーブルや排他制御中のテーブルは RecordCount 参照でエラーになる
GetRecordCountSafely = tdf.RecordCount
Exit Function
Catch:
GetRecordCountSafely = “N/A (参照不可)”
End Function

3. チーフアーキテクトが実践するメモリ最適化とリソース管理

Access VBAにおける最大の悪習は、`CurrentDb.TableDefs…` のようにオブジェクト参照をチェーンさせ、参照をインスタンス変数に保持したまま放置することである。これにより、Accessの背後で動作するJET/ACEのCOMコンポーネントがメモリ上に居座り続け、パフォーマンス低下や「リソース不足」エラーを引き起こす。

メモリリークを防ぐための鉄則

1. `CurrentDb` は必ずローカル変数にキャッシュする
`CurrentDb` を呼び出すたびに、新しいデータベースインスタンスがヒープ上に生成される。必ず `Set db = CurrentDb()` として変数に受け、プロシージャ終了時に `Set db = Nothing` で解放すること。
2. ループ内でのオブジェクト変数破棄
`For Each` やインデックスループ内で `TableDef` や `QueryDef` などのオブジェクトを取得する場合、ループの各イテレーションの終わり、あるいは次の代入の前に必ず `Set tdf = Nothing` を明示する。
3. エラーハンドリング時の確実なクリーンアップ
例外(Error)が発生して処理が中断した場合でも、`On Error GoTo` を用いて必ず解放処理(CleanUpラベル)へルーティングする構造を義務付ける。

4. 実務での活用例:隠しテーブルを利用した「アプリケーション設定の永続化」

一般ユーザーに触られたくない、しかしマスターデータとして保持したい環境設定や、アプリケーションの最終同期日時などを格納する領域として、あえて「隠しテーブル」をプログラムから動的に作成・管理する手法は非常に有効である。

以下は、指定した名前の隠しテーブルが存在しない場合に自動生成し、UIから不可視化する実務的スニペットである。

‘ ==============================================================================
‘ 処理名 : EnsureHiddenConfigTable
‘ 概要 : 隠し設定テーブルが存在しない場合に作成し、隠し属性を付与する。
‘ ==============================================================================
Public Sub EnsureHiddenConfigTable()
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim fld As DAO.Field
Const TARGET_TABLE As String = “Sys_AppConfig”

Set db = CurrentDb()

On Error GoTo ErrorHandler

‘ テーブルの存在確認
If Not TableExists(db, TARGET_TABLE) Then
‘ テーブル定義の新規作成
Set tdf = db.CreateTableDef(TARGET_TABLE)

‘ フィールドの追加 (ConfigKey と ConfigValue)
Set fld = tdf.CreateField(“ConfigKey”, dbText, 50)
tdf.Fields.Append fld
Set fld = tdf.CreateField(“ConfigValue”, dbMemo)
tdf.Fields.Append fld

‘ テーブルの登録
db.TableDefs.Append tdf

‘ 隠し属性(dbHiddenObject)を付与する
‘ 注: 一度テーブルをAppendした後でなければAttributesの変更は反映されない
tdf.Attributes = tdf.Attributes Or dbHiddenObject

Debug.Print “隠し設定テーブル ‘” & TARGET_TABLE & “‘ を正常に作成しました。”
Else
Debug.Print “隠し設定テーブル ‘” & TARGET_TABLE & “‘ は既に存在します。”
End If

CleanUp:
If Not tdf Is Nothing Then Set tdf = Nothing
If Not db Is Nothing Then Set db = Nothing
Exit Sub

ErrorHandler:
MsgBox “設定テーブルの作成に失敗しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub

‘ テーブル存在確認用のヘルパー関数
Private Function TableExists(ByRef db As DAO.Database, ByVal tableName As String) As Boolean
Dim tdf As DAO.TableDef
On Error Resume Next
Set tdf = db.TableDefs(tableName)
TableExists = (Err.Number = 0)
Set tdf = Nothing
On Error GoTo 0
End Function

この手法を用いることで、ユーザーが誤って設定テーブルを削除したり、Excel等で勝手に書き換えてアプリケーションをクラッシュさせるリスクを根本から断つことができる。

総括

システムテーブルや隠しテーブルの制御は、Access VBAの深層に踏み込むスリリングな領域である。しかし、それは「危険だから触らない」のではなく、「ルールとメカニズムを完全に入れ込んだ上で、堅牢に制御する」べき対象なのだ。

オブジェクトのライフサイクルを支配し、適切なビット演算とDAOによるハンドリングを習得した者だけが、レガシーAccessシステムの限界を突破し、真に安定したエンタープライズ級のアーキテクチャを構築できる。現場のコードにただちにこれらの知見を組み込み、システムを次のステージへと引き上げてほしい。

タイトルとURLをコピーしました