
Анализ планов и форматирование запросов PostgreSQL можно выполнять в VS Code, используя explain.tensor.ru и плагин, о котором пойдет речь ниже.
Установка

Для выполнения запросов наш плагин использует другое популярное расширение - SQLTools , поэтому вначале необходимо настроить в нем подключение к PostgreSQL и затем установить Explain PostgreSQL из приложения или командной строки:
code --install-extension TensorCompanyLtd.explain-postgresql
Форматирование запроса
В контекстном меню редактора SQL или в списке Code Actions выбираем Format SQL

Анализ запроса
В контекстном меню редактора SQL или в списке Code Actions выбираем Explain Analyze , при этом запрос выполнится на сервере PG (настроенном в SQLTools) и полученный план отправится на анализ в сервис explain.tensor.ru через публичное API , результат откроется в новом окне

Настройка
В настройках можно указать адрес сайта и опции выполнения анализа:

Разработка
Для создания каркаса плагина установим утилиты по инструкции , зададим параметры и установим рекомендуемые расширения:
npx --package yo --package generator-code -- yo code ? What type of extension do you want to create? New Extension (TypeScript) ? What's the name of your extension? Explain PostgreSQL ? What's the identifier of your extension? explain-postgresql ? What's the description of your extension? Analyzes EXPLAIN plan from PostgreSQL ? Initialize a git repository? Yes ? Bundle the source code with webpack? Yes ? Which package manager to use? npm code explain-postgresql
Проверяем - нажимаем F5 и в новом окне набираем команду Ctrl-Shift-P > Hello World , должно появиться окно с сообщением:

Главным файлом плагина является его манифест - package.json, пропишем в нем зависимость от плагина SQLTools для его установки вместе с нашим плагином:
"extensionDependencies": [ "mtxr.sqltools" ]
в параметре activationEvents определяем условие запуска плагина, в нашем случае редактирование SQL файлов:
"activationEvents": [ "onLanguage:sql" ]
в параметре contributes мы расширяем функционал VSCode - добавляем команды, пункты меню, настройки и прочие. Например, добавим три команды - форматирование текста, анализ плана запроса и открытие сайта:
"commands": [ { "command": "explain-postgresql.formatsql", "title": "Format SQL", "icon": "icons/EP.png" }, { "command": "explain-postgresql.explainanalyze", "title": "Explain Analyze", "icon": "icons/EP.png" }, { "command": "explain-postgresql.explainsite", "title": "Explain PostgreSQL", "icon": "icons/EP.png" } ]
Добавим два пункта в контекстном меню редактора - Format SQL и Explain Analyze, а также кнопку открытия сайта на титульной панели, условием отображения поставим язык SQL, а группу - navigation для размещения в топе и 1_modification для размещения наших команд первыми в списке:
"menus": { "editor/title": [ { "when": "resourceLangId == sql", "command": "explain-postgresql.explainsite", "group": "navigation", "icon": "./icons/EP.png" } ], "editor/context": [ { "when": "resourceLangId == sql", "command": "explain-postgresql.formatsql", "group": "1_modification", "icon": "./icons/EP.png" }, { "when": "resourceLangId == sql", "command": "explain-postgresql.explainanalyze", "group": "1_modification", "icon": "./icons/EP.png" } ] }
Настройки плагина храним в виде объекта, например URL сайта:
"configuration": { "type": "object", "title": "Explain PostgreSQL", "properties": { "explain-postgresql.url": { "scope": "resource", "type": "string", "default": "https://explain.tensor.ru", "description": "API site", "order": 1 } } }
для доступа к настройкам используем функцию:
const url = vscode.workspace.getConfiguration('explain-postgresql').get('url');
В параметре main манифеста задается точка входа в плагин - файл extension.js , в котором экспортируется входная функция activate. В качестве параметра она получает контекст , в котором хранятся приватные параметры плагина, например путь к его каталогу extensionUri , который мы используем для получения пути к иконке панели:
panel.iconPath = vscode.Uri.joinPath(Context.extensionUri, "icons/EP.png");
В этой функции регистрируем команды, которые задали в манифесте:
vscode.commands.registerCommand('explain-postgresql.formatsql', formatCommand); vscode.commands.registerCommand('explain-postgresql.explainanalyze', explainCommand); vscode.commands.registerCommand('explain-postgresql.explainsite', explainsiteCommand);
В коде команды форматирования мы берем выделенный участок или весь текст, форматируем, используя API explain.tensor.ru и заменяем текст:
export async function formatCommand(): Promise<void> { let editor = vscode.window.activeTextEditor; if (editor) { let validFullRange: vscode.Range; let selection = editor.selection; let text = editor.document.getText(selection); if (!text) { text = editor.document.getText(); let invalidRange = new vscode.Range(0, 0, editor.document.lineCount, 0); validFullRange = editor.document.validateRange(invalidRange); } try { let formatted = await beautifier(text); // получаем форматированный текст editor.edit(editBuilder => { editBuilder.replace(validFullRange || selection, formatted); }) } catch (e) { vscode.window.showErrorMessage(`${e}`, {modal: true}); } } }
В команде анализа запроса мы также берем текст запроса из текущего редактора и добавляем к нему EXPLAIN со списком опций, заданных в настройках плагина.
Затем запускаем команду executeQuery из плагина SQLTools, которая выполнит запрос на подключенном сервере и вернет план запроса.
Текст запроса вместе с его планом выполнения отправляем на анализ в API explain.tensor.ru и открываем результат в новой панели:
export async function explainCommand(): Promise<void> { let editor = vscode.window.activeTextEditor; let selection = editor.selection; let query = editor.document.getText(selection); if (!query) { query = editor.document.getText(); } const explainSettings = vscode.workspace.getConfiguration('explain-postgresql').get('explain'); let options = Object.keys(explainSettings).filter(key => explainSettings[key]).join(','); let plan = await vscode.commands.executeCommand(`sqltools.executeQuery`, `EXPLAIN (${options}) ${query}`); try { let url = await explain(plan, query); // отправляем план на анализ в API explain const apiUrl = vscode.workspace.getConfiguration('explain-postgresql').get('url'); await createView(`${apiUrl}${url}`); } catch(e) { vscode.window.showErrorMessage(`${e}`, {modal: true}); } }
Для создания панели с результатами анализа используем Webview, которая фактически является элементом iframe , таким образом мы можем размещать в нем свой HTML-код.
Добавим в панель свой iframe, в атрибуте src которого укажем URL документа с результатом анализа:
async function getHtml (url: string) { return `<!DOCTYPE html><html><head></head> <body style="margin:0px;padding:0px;overflow:hidden"> <div> <iframe sandbox="allow-scripts allow-forms allow-same-origin allow-downloads" src=${url} style="width:100vw;height:100vh;border: none;display: block;"></iframe> </div> </body></html>`; } export default async function createView(url: string) { let panel = vscode.window.createWebviewPanel( 'explainAnalyze', 'Explain Analyze', vscode.ViewColumn.Active, { enableScripts: true, retainContextWhenHidden: true, } ); panel.webview.html = getHtml(url); panel.iconPath = vscode.Uri.joinPath(Context.extensionUri, "icons/EP.png"); }
Для создания Code Actions используем CodeActionProvider :
export class Actions implements vscode.CodeActionProvider { public static readonly providedCodeActionKinds = [ vscode.CodeActionKind.Refactor, ]; provideCodeActions(document: vscode.TextDocument, range: vscode.Range | vscode.Selection, context: vscode.CodeActionContext, token: vscode.CancellationToken): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> { const commandFormatSQL = this.createCommand('explain-postgresql.formatsql', "Format SQL", "Formatting SQL text"); const commandExplainAnalyze = this.createCommand('explain-postgresql.explainanalyze', "Explain Analyze", "Explain query plan"); return [ commandFormatSQL, commandExplainAnalyze, ]; } private createCommand(command: string, title: string, tooltip: string): vscode.CodeAction { const action = new vscode.CodeAction(title, vscode.CodeActionKind.Refactor); action.command = {command, title, tooltip}; return action; } }
регистрируем его в функции activate вместе с командами:
vscode.languages.registerCodeActionsProvider('sql', new Actions(), { providedCodeActionKinds: Actions.providedCodeActionKinds })
Для создания кнопки на панели статусов создаем новый StatusBarItem также в activate :
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10); statusBarItem.command = 'explain-postgresql.explainanalyze'; statusBarItem.text = 'Explain Analyze'; statusBarItem.show();
Отладка
Для отладки панелей удобно пользоваться встроенным WebView Developer Tools:


Сборка и публикация плагина
Для сборки и публикации плагина установим утилиту vsce :
npm install -g @vscode/vsce
Указываем версию в параметре version в package.json и запускаем сборку:
vsce package Packaged: explain-postgresql/explain-postgresql-1.0.5.vsix (12 files, 319.64KB)
VS Code Extension Marketplace использует Azure DevOps , а утилита vsce публикует только с использованием персональных токенов этого сервиса, поэтому для его получения регистрируемся на dev.azure.com и создаем новую организацию:

В настройках организации создаем новый токен:

Копируем созданный токен, заходим на сайт Visual Studio Marketplace publisher management page с тем же логином, которым создавали токен, и создаем публикацию:

При создании потребуется указать уникальный ID, который пропишем в package.json в параметре publisher:
"publisher": "TensorCompanyLtd"
Проверяем публикацию из утилиты vsce , используя созданный токен:
vsce login TensorCompanyLtd https://marketplace.visualstudio.com/manage/publishers/ Personal Access Token for publisher 'TensorCompanyLtd': **************************************************** The Personal Access Token verification succeeded for the publisher 'TensorCompanyLtd'.
И публикуем наш плагин:
vsce publish
Проверяем - плагин должен появиться на сайте Visual Studio Marketplace publisher management page и через несколько минут в списке расширений в VSCode

Код плагина опубликован под лицензией MIT.
