Анализ планов и форматирование запросов 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.