本仕様は SSMS 拡張機能における「新規クエリウィンドウ作成時の SET TRANSACTION ISOLATION LEVEL 自動挿入」の挙動と、現行実装(src\\IsolationLevelAutoInsert)に基づく検知/挿入方法を定義する。
- Auto Insert: 新規クエリウィンドウ作成時に、分離レベル設定ステートメントを自動で挿入する機能
- Manual Insert: 既存のクエリエディターに対して手動で挿入する機能 (本仕様では補足のみ)
- RDT: Running Document Table(Visual Studio/SSMS が開いているドキュメントを管理するテーブル)
- docCookie: RDT がドキュメントを識別するための ID
トップレベルメニュー: SSMS Extensions
配下メニュー例:
Transaction Isolation LevelAuto InsertRead UncommittedRead CommittedSnapshotRepeatable ReadSerializableNone (Disable Auto-Insert)
- Auto Insert の現在選択中の項目にチェックマークを表示する。
None (Disable Auto-Insert)を選択した場合、他の分離レベルのチェックは外れる。
- Auto Insert の選択値はユーザー単位で永続化する。
- 保存先は Visual Studio Shell の UserSettings(
ShellSettingsManager/WritableSettingsStore)を使用する。 - 既定値は
NONE(UI 上はNone (Disable Auto-Insert))とする。
- ShellSettingsManager Class: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.settings.shellsettingsmanager?view=visualstudiosdk-2022
- VS の設定ストアへアクセスするための入口クラス。
- ShellSettingsManager.GetWritableSettingsStore: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.settings.shellsettingsmanager.getwritablesettingsstore?view=visualstudiosdk-2022
SettingsScopeを指定して書き込み可能ストアを取得する。
- WritableSettingsStore Class: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.settings.writablesettingsstore?view=visualstudiosdk-2022
- ユーザー設定の作成/取得/更新/削除 API。
- Writing to the User Settings Store: https://learn.microsoft.com/en-us/visualstudio/extensibility/writing-to-the-user-settings-store?view=vs-2022
- ユーザー設定ストアへ保存する手順の解説。
- WritableSettingsStore.CreateCollection: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.settings.writablesettingsstore.createcollection?view=visualstudiosdk-2022
- 設定の論理フォルダ(Collection)を作成する。
- WritableSettingsStore.SetString: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.settings.writablesettingsstore.setstring?view=visualstudiosdk-2022
- 文字列値の保存 API。
- SettingsStore.GetString: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.settings.settingsstore.getstring?view=visualstudiosdk-2022
- 文字列値の取得 API(デフォルト値指定の overload あり)。
本機能の実装は、SSMS(Visual Studio Shell ベース)のエディター拡張ポイントを利用し、以下の主要コンポーネントで構成される。
IsolationLevelAutoInsertPackage- SSMS 起動時に自動ロードされるエントリポイント。
- 設定(
SettingsManager)と RDT 監視(EditorMonitor)を初期化する。
SettingsManager- Auto Insert の選択状態(
IsolationLevel)を UserSettings に永続化する。
- Auto Insert の選択状態(
EditorMonitor- Running Document Table (RDT) を監視し、「新規に作成された(= 空の)SQL エディター」を検知して自動挿入を実行する。
- docCookie →
IVsTextBuffer→ITextBufferの変換を行い、バッファを直接編集する。 - 重複挿入防止のため
ITextBuffer.Propertiesにフラグを保持する。 - 挿入後、アクティブビューのキャレットを 2 行目へ移動する。
本 SPEC では learn.microsoft.com の一次情報を「実装の根拠」として参照している。各ドキュメントは、主に以下の用途で利用している。
- 設定永続化(4 章)
ShellSettingsManager/WritableSettingsStoreを使ってユーザー設定を保存/読み込みするため。- 対応する参考ドキュメント: 4.1
- パッケージのロード/初期化(6.1)
AsyncPackageとProvideAutoLoad/PackageRegistration/ProvideMenuResourceの役割を整理し、SSMS 起動時に AutoLoad される構成を説明するため。- 対応する参考ドキュメント: 6.5(AsyncPackage/ProvideAutoLoad/ProvideMenuResource)
- 新規クエリエディターの検知(6.2〜6.4)
- Running Document Table (RDT) と
IVsRunningDocTableEventsを購読し、docCookie を起点にドキュメントを特定するため。 OnBeforeDocumentWindowShowの「ウィンドウ/フレーム」概念の背景理解のため。- 対応する参考ドキュメント: 6.5(RDT/Events/Document windows)
- Running Document Table (RDT) と
- 文字挿入(7.1〜7.3)
IVsTextBuffer(legacy)からITextBuffer(MEF/WPF)へ変換し、ITextEditで先頭へ挿入する方法を説明するため。- 対応する参考ドキュメント: 7.6(EditorAdapters/ITextBuffer/ITextEdit)
- キャレット移動(7.5)
IVsTextManager.GetActiveView/IVsTextView.SetCaretPosを使ったカーソル移動の根拠として。- 対応する参考ドキュメント: 7.6(IVsTextManager/IVsTextView)
本機能は「SSMS 起動後にパッケージがロードされ、RDT イベントをトリガーとして空の SQL エディターに 1 回だけ挿入する」という流れで動作する。 特に RDT イベントは複数回発火する可能性があるため、バッファ単位のフラグで冪等性(同じバッファに 2 回挿入しない)を担保している。
- SSMS 起動 →
IsolationLevelAutoInsertPackageが AutoLoad SettingsManagerがユーザー設定から分離レベルをロードEditorMonitorが RDT(Running Document Table)イベントを購読- 新規クエリ作成などでドキュメントが生成されると、RDT イベントが発火
TryInsertText(docCookie)が docCookie から対象バッファを解決- フィルタ(SQL 判定/空バッファ/未挿入/設定有効)をすべて満たす場合のみ、先頭へ
SET TRANSACTION ISOLATION LEVEL ...を挿入 - 挿入済みフラグを
ITextBuffer.Propertiesに保存 - ビュー生成タイミングの差を吸収するため、短い遅延後にキャレットを 2 行目へ移動
[SSMS 起動 / Package AutoLoad]
↓
IsolationLevelAutoInsertPackage.InitializeAsync
↓
SettingsManager.LoadSettings
↓
EditorMonitor(IVsRunningDocTableEvents を購読)
↓
RDT event (OnAfterFirstDocumentLock / OnBeforeDocumentWindowShow)
↓
TryInsertText(docCookie)
↓
GetDocumentInfo → docData → IVsTextBuffer → ITextBuffer
↓
(フィルタ: SQL/空/未挿入/設定有効)
↓
ITextEdit.Insert(0, "SET TRANSACTION ISOLATION LEVEL ...") → Apply
↓
ITextBuffer.Properties に挿入済みフラグ
↓
(遅延) IVsTextView.SetCaretPos(1,0)
- パッケージ
IsolationLevelAutoInsertPackageはProvideAutoLoadにより SSMS 起動時(EmptySolution / NoSolution)にロードされる。 InitializeAsync(UI スレッド)で以下を行う。SettingsManagerを初期化し、設定をロード- Auto Insert 選択用コマンド(Select*)と手動挿入コマンド(Insert*)を初期化
EditorMonitorを生成し、RDT イベント購読を開始
対象: src\IsolationLevelAutoInsert\IsolationLevelAutoInsertPackage.cs
// パッケージは AsyncPackage としてロードされる
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[ProvideMenuResource("Menus.ctmenu", 1)]
[ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids.EmptySolution, PackageAutoLoadFlags.BackgroundLoad)]
[ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]
public sealed class IsolationLevelAutoInsertPackage : AsyncPackage
{
private SettingsManager _settingsManager;
private EditorMonitor _editorMonitor;
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
await base.InitializeAsync(cancellationToken, progress);
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
_settingsManager = new SettingsManager(this);
_settingsManager.LoadSettings();
// ... Select* / Insert* コマンド初期化 ...
// RDT 監視を開始
_editorMonitor = new EditorMonitor(_settingsManager, this);
}
}EditorMonitorはIVsRunningDocTableEventsを実装し、SVsRunningDocumentTable(IVsRunningDocumentTable)へ購読する。
対象: src\IsolationLevelAutoInsert\EditorMonitor.cs
// RunningDocumentTable を取得
var rdt = serviceProvider.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
if (rdt == null)
{
DiagnosticLogger.Log("EditorMonitor: Failed to get IVsRunningDocumentTable");
return;
}
_rdt = rdt;
_rdt.AdviseRunningDocTableEvents(this, out _rdtCookie);- 以降、SSMS がドキュメントをオープン/表示するタイミングで RDT からコールバックされる。
現行実装では、検知の取りこぼしを減らすために 2 つのイベントから同じ挿入処理を呼び出す。
OnAfterFirstDocumentLock(docCookie, ...)
- ドキュメントが初めてロックされたタイミングで呼ばれる。
OnBeforeDocumentWindowShow(docCookie, fFirstShow, ...)
- ドキュメントウィンドウが表示される直前に呼ばれる。
fFirstShow != 0(初回表示)の場合のみ処理する。
対象: src\IsolationLevelAutoInsert\EditorMonitor.cs
public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
{
try
{
ThreadHelper.ThrowIfNotOnUIThread();
TryInsertText(docCookie);
}
catch (Exception ex)
{
DiagnosticLogger.LogError($"OnAfterFirstDocumentLock failed for docCookie={docCookie}", ex);
}
return 0;
}
public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
{
try
{
ThreadHelper.ThrowIfNotOnUIThread();
if (fFirstShow != 0)
{
TryInsertText(docCookie);
}
}
catch (Exception ex)
{
DiagnosticLogger.LogError($"OnBeforeDocumentWindowShow failed for docCookie={docCookie}", ex);
}
return 0;
}TryInsertText(docCookie) の内部では、次の条件をすべて満たした場合のみ挿入を行う。
- 設定が有効(
SettingsManager.IsolationLevel != "NONE") - RDT から
docData(ドキュメントデータの COM ポインタ)を取得できる docDataがIVsTextBufferとして扱える(テキストエディターである)- SQL ファイル判定
moniker(パス)が空でない場合: 拡張子が.sqlであることmonikerが空/NULL の場合: 「無題(新規)」の可能性があるため許可(※この挙動は現行実装に合わせる)
- バッファが空(
ITextBuffer.CurrentSnapshot.Length == 0)- 既存ファイルを開いた場合やテンプレート展開済みの場合は挿入しない
- 既に挿入済みでない
ITextBuffer.Propertiesに挿入済みフラグがある場合はスキップ
- Use AsyncPackage to load VSPackages in the background: https://learn.microsoft.com/en-us/visualstudio/extensibility/how-to-use-asyncpackage-to-load-vspackages-in-the-background?view=vs-2022
- AsyncPackage の背景ロード手順と注意点。
- AsyncPackage Class: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.asyncpackage?view=visualstudiosdk-2022
- AsyncPackage の API リファレンス。
- ProvideAutoLoadAttribute Class: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.provideautoloadattribute?view=visualstudiosdk-2022
- UIContext に応じたパッケージ自動ロード属性。
- PackageRegistrationAttribute.AllowsBackgroundLoading: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.packageregistrationattribute.allowsbackgroundloading?view=visualstudiosdk-2022
- 背景ロード可否の登録属性。
- ProvideMenuResourceAttribute Class: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.providemenuresourceattribute?view=visualstudiosdk-2022
- .vsct のコマンド/メニューを登録する属性。
- Document windows (internals): https://learn.microsoft.com/ja-jp/visualstudio/extensibility/internals/document-windows?view=vs-2022
- ドキュメントウィンドウ/フレームの内部構造の解説。
- IVsRunningDocumentTable Interface: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.interop.ivsrunningdocumenttable?view=visualstudiosdk-2022
- RDT の COM インターフェイス定義。
- IVsRunningDocumentTable.AdviseRunningDocTableEvents: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.interop.ivsrunningdocumenttable.adviserunningdoctableevents?view=visualstudiosdk-2022
- RDT イベント購読 API。
- IVsRunningDocumentTable.GetDocumentInfo: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.interop.ivsrunningdocumenttable.getdocumentinfo?view=visualstudiosdk-2022
- docCookie から moniker/docData 等を取得する。
- IVsRunningDocTableEvents Interface: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.interop.ivsrunningdoctableevents?view=visualstudiosdk-2022
- RDT イベントのコールバック定義。
- IVsRunningDocTableEvents.OnAfterFirstDocumentLock: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.interop.ivsrunningdoctableevents.onafterfirstdocumentlock?view=visualstudiosdk-2022
- 最初の read/edit lock 取得後に呼ばれる。
- IVsRunningDocTableEvents.OnBeforeDocumentWindowShow: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.interop.ivsrunningdoctableevents.onbeforedocumentwindowshow?view=visualstudiosdk-2022
- ドキュメントウィンドウ表示直前に呼ばれる。
TryInsertText(docCookie) は RDT からドキュメントの詳細を取得し、docData(IUnknown)から IVsTextBuffer → ITextBuffer へ変換する。
対象: src\IsolationLevelAutoInsert\EditorMonitor.cs
_rdt.GetDocumentInfo(docCookie,
out uint flags, out uint readLocks, out uint editLocks,
out string moniker, out IVsHierarchy hierarchy, out uint itemId, out IntPtr docData);
if (docData == IntPtr.Zero)
{
DiagnosticLogger.Log("DocData is null");
return;
}
// docData(IUnknown) → IVsTextBuffer
var textBuffer = Marshal.GetObjectForIUnknown(docData) as IVsTextBuffer;
Marshal.Release(docData);
if (textBuffer == null)
{
DiagnosticLogger.Log("Not a text buffer");
return;
}
// IVsTextBuffer → ITextBuffer(WPF/MEF 側)
var wpfTextBuffer = _editorAdaptersFactory?.GetDataBuffer(textBuffer);
if (wpfTextBuffer == null)
{
DiagnosticLogger.Log("Failed to get WPF text buffer");
return;
}- 挿入は
ITextBuffer.CreateEdit()によりITextEditを作成し、先頭へInsert(0, text)を行う。
対象: src\IsolationLevelAutoInsert\EditorMonitor.cs
// 挿入するテキストを設定に基づいて決定
string insertText = GetIsolationLevelStatement(isolationLevel);
if (string.IsNullOrEmpty(insertText))
{
DiagnosticLogger.Log($"Unknown isolation level: {isolationLevel}. Skipping insertion.");
return;
}
// テキストを挿入
using (var edit = wpfTextBuffer.CreateEdit())
{
edit.Insert(0, insertText);
edit.Apply();
}- これは「VS/SSMS のエディターが管理するバッファ」に対する編集であり、ユーザーの手編集と同じレイヤーで反映される(ロックや特殊なモードはかけない)。
-
同一バッファに対して複数回挿入しないため、
ITextBuffer.Propertiesにフラグを保存する。 -
Key:
IsolationLevelAutoInsert.Inserted
対象: src\IsolationLevelAutoInsert\EditorMonitor.cs
private const string InsertedFlagKey = "IsolationLevelAutoInsert.Inserted";
if (wpfTextBuffer.Properties.ContainsProperty(InsertedFlagKey))
{
DiagnosticLogger.Log("Text already inserted. Skipping.");
return;
}
wpfTextBuffer.Properties.AddProperty(InsertedFlagKey, true);このため、RDT イベントが複数回発火しても「最初の 1 回だけ」挿入される。
選択された分離レベルに応じて、以下のいずれかを挿入する。
- Read Uncommitted:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
- Read Committed:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
- Snapshot:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
- Repeatable Read:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
- Serializable:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
- 現行実装の挿入文字列は末尾が改行 1 つ(
\r\n)である。
SET TRANSACTION ISOLATION LEVEL <LEVEL>;- 大文字/小文字は上記の固定表記とする(ユーザー体験の一貫性のため)。
- 挿入後、カーソルを 2 行目先頭へ移動する。
- ビューの生成タイミング差を吸収するため、100ms 遅延した非同期処理で実行する。
await Task.Delay(100);
textManager.GetActiveView(1, null, out IVsTextView textView);
textView.SetCaretPos(1, 0); // (line=1, col=0)- IVsEditorAdaptersFactoryService.GetDataBuffer: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.editor.ivseditoradaptersfactoryservice.getdatabuffer?view=visualstudiosdk-2022
IVsTextBuffer→ITextBuffer変換 API。
- ITextBuffer Interface: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.text.itextbuffer?view=visualstudiosdk-2022
- エディターのテキストバッファ API。
- ITextBuffer.CreateEdit: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.text.itextbuffer.createedit?view=visualstudiosdk-2022
- 編集トランザクション(
ITextEdit)開始。
- 編集トランザクション(
- ITextEdit Interface: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.text.itextedit?view=visualstudiosdk-2022
- Insert/Replace/Delete など編集操作の定義。
- ITextEdit.Insert: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.text.itextedit.insert?view=visualstudiosdk-2022
- 指定位置への挿入 API。
- IVsTextManager.GetActiveView: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.textmanager.interop.ivstextmanager.getactiveview?view=visualstudiosdk-2022
- アクティブな
IVsTextViewを取得する。
- アクティブな
- IVsTextView.SetCaretPos: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.textmanager.interop.ivstextview.setcaretpos?view=visualstudiosdk-2022
- キャレット(カーソル)位置を移動する。
- RDT イベントハンドラおよび挿入処理は
try/catchで囲み、例外が起きても SSMS 全体が落ちないようにする。 - Auto Insert の実行失敗をユーザーに MessageBox で通知しない(体験阻害を避ける)。
DiagnosticLogger を用い、少なくとも以下をログへ出力する。
- パッケージ初期化開始/完了
- RDT イベント購読の成否
- イベント発火(
docCookie) - 設定値(
IsolationLevel) - スキップ理由
- 無効(NONE)
- docData が取れない
- SQL 以外
- バッファ非空
- 既に挿入済み
- 例外情報(スタックトレース)
- Auto Insert=Snapshot を選択し、「New Query」等で新規クエリウィンドウを開く。
- 先頭に
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;が挿入される。 - カーソルが 2 行目先頭へ移動する。
- 先頭に
- Auto Insert=None を選択し、新規クエリウィンドウを開く。
- 何も挿入されない。
- 既存の
.sqlファイルを開く(内容あり)。- バッファが空でないため、自動挿入されない。
- SSMS 再起動後も Auto Insert の選択が保持される。
- 分離レベルは接続単位の設定であり、実行対象 DB の設定(例: Snapshot の可否)によっては SQL 実行時にエラーとなる場合がある。
- 本機能は「挿入」を行うのみで、DB 側の設定検証はしない。