VisualStudio Code extension
VisualStudio Code extension

Анализ планов и форматирование запросов 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 , должно появиться окно с сообщением:

Hello World
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:

запускаем devtools
запускаем devtools
отладка панели
отладка панели

Сборка и публикация плагина

Для сборки и публикации плагина установим утилиту 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 с тем же логином, которым создавали токен, и создаем публикацию:

create publisher
create publisher

При создании потребуется указать уникальный 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.

Комментарии (0)