
Для пользователей explain.tensor.ru - нашего сервиса визуализации PostgreSQL-планов, в дополнение к плагину Jetbrains мы создали еще один - с возможностью форматировать запросы и анализировать планы в Eclipse IDE и DBeaver.
Установка плагина
В Eclipse установку удобнее выполнить через Marketplace , при этом дополнительно установятся необходимые компоненты из категории Data Tools Platform и Chromium Integration. Если в Eclipse присутствует плагин DBeaver, то к нему также добавятся функции форматирования и анализа запросов.

В отдельном приложении DBeaver надо добавить URL update-сайта с плагином. Для этого необходимо скачать файл:
curl -sLO https://explain.tensor.ru/downloads/plugins/eclipse/bookmarks.xmlимпортировать его в меню Window -> Preferences -> General -> Install/Update -> Available Software Sites:

затем в меню Help -> Install New Software в списке сайтов выбрать "Explain PostgreSQL" и установить плагин "Explain PostgreSQL for DBeaver":

Установка возможна также из командной строки, текст команд для разных систем можно посмотреть на сайте explain.tensor.ru.
Форматирование запроса
Форматирование доступно в перспективах Database Development и DBeaver в контекстном меню редактора SQL:


Анализ запроса
В Database Development в контекстном меню выбираем Get Execution Plan, при этом запрос выполнится на подключенном сервере и полученный план отправится на анализ в сервис explain.tensor.ru через публичное API , результат откроется в новом окне:

В DBeaver выбираем Explain Execution Plan на тулбаре или в контекстном меню Execute -> Explain Execution Plan

Настройка плагина
При необходимости можно поменять сайт настройках Window -> Preferences -> Explain PostgreSQL , например explain-postgresql.com или локальный для варианта self hosted:

Для Mac с процессором Apple опция использования внешнего браузера установится автоматически из-за отсутствия поддержки в Chromium Integration.
Разработка плагина
Устанавливаем Eclipse IDE for RCP and RAP Developers и создаем новый Plug-in Project без использования шаблонов и оставив все значения по умолчанию.
При этом в каталоге проекта будет создан подкаталог META-INF с файлом MANIFEST.MF. В этом файле хранится мета-информация о плагине, а также он может содержать информацию о предоставляемом коде и список необходимых для его работы плагинов.
Вообще, в Eclipse управление плагинами (регистрация, обновление и удаление, управление зависимостями между плагинами и организация их взаимодействия) выполняется по спецификации OSGi и реализуется подсистемой Equinox. В понятиях OSGi плагин является сборкой (bundle), описание которой содержится в MANIFEST.MF . Однако в этом файле содержатся только прямые зависимости - во время выполнения Eclipse активирует плагин только после загрузки всех зависимых модулей.
Другим типом взаимодействия плагинов являются точки расширения. Их описание содержится в файле plugin.xml . Плагин может добавить функционал, описав его в этом файле в декларативном виде. При этом код и данные плагина могут подгружаться только во время использования этого функционала. По сути в этом файле определяется прикладной функционал плагина. Редактировать его можно как обычный текстовый файл, но удобнее это делать с помощью Plug-in Manifest Editor- открыв в нем MANIFEST.MF и перейдя на вкладку Extensions.
Создание панелей
Для создания панелей (view) мы используем плагин org.eclise.ui , а точнее его точку расширения org.eclipse.ui.views - добавляем ее на вкладке Extensions , при этом Eclipse предложит добавить плагин org.eclipse.ui в зависимости, соглашаемся :

После добавления в контекстном меню расширения выбираем New -> view и создаем новую панель:

Здесь дополнительно укажем категорию, в которой будет отображаться панель, можно выбрать из списка, возьмем org.eclipse.datatools (Data Management)
И создаем класс ru.tensor.explain.eclipse.views.PostgresPlanView - просто пропишем имя в поле class и кликнем по нему:

Здесь оставляем все по умолчанию и в созданном классе переопределяем методы:
код класса PostgresPlanView
public class PostgresPlanView extends ViewPart {
private Browser fBrowser;
@Override
public void createPartControl(Composite parent) {
fBrowser = new Browser(parent, SWT.NONE);
fBrowser.setUrl("https://explain.tensor.ru");
}
@Override
public void setFocus() {
fBrowser.setFocus();
}
}Проверяем - пробуем запустить плагин:

выбираем Window -> Show view -> Other и в категории Data Management видим нашу панель:

Добавление панели в перспективу
Добавляем расширение org.eclipse.ui.pespectiveExtensions и в нем создаем дополнение к перспективе редактора SQL - добавляем нашу панель рядом с панелью результатов запроса:

Добавление команды в меню
По аналогии с панелью добавляем новые расширения org.eclipse.ui.menus и org.eclipse.ui.commands
В контекстном меню расширения commands добавляем новую команду Format SQL :

в контекстном меню расширения menus добавляем menuContribution с locationURI = popup:org.eclipse.ui.popup.any?before=additions и в его контексте добавляем новую команду:

locationURI позволяет выбрать место, куда будет добавлена новая команда, здесь мы добавили команду в контекстное меню перед группой команд additions , подробнее об этом в описании точки расширения:

В таком виде наша команда будет показываться в любом контекстном меню, а нам нужно показывать ее только в контекстном меню редактора SQL, для этого добавляем условие visibleWhen в котором activeEditorInput будет экземпляром (instanceof) ISQLEditorInput :

Для создания обработчика команды добавляем расширение org.eclipse.ui.handlers и в нем, по аналогии с меню, добавляем новый обработчик с условием enabledWhen :

Подробнее о переменных и выражениях в условиях можно почитать здесь.
В поле class обработчика пишем его имя и создаем новый класс с параметрами по умолчанию, реализовав метод execute , в котором форматируем текст запроса:
код класса FormatSQLhandler
public class FormatSQLhandler implements IHandler {
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
final IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindowChecked(event);
final ITextEditor editor = (ITextEditor) window.getActivePage().getActiveEditor();
if (editor == null) {
return null;
}
String workSql = null;
final boolean isSelection;
if (editor.getSelectionProvider().getSelection() instanceof TextSelection) {
workSql = ((TextSelection) editor.getSelectionProvider().getSelection()).getText();
}
if (workSql == null || "".equals(workSql)) {
workSql = editor.getDocumentProvider().getDocument(editor.getEditorInput()).get();
isSelection = false;
} else {
isSelection = true;
}
final String sql = workSql;
Job job = new Job("Explain PostgreSQL Format Thread") {
@Override
protected IStatus run(IProgressMonitor monitor) {
window.getWorkbench().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
try {
String fmtText = ""; // get formatted text
if (fmtText.startsWith("Error")) {
throw new Exception (fmtText);
}
IDocument doc = editor.getDocumentProvider().getDocument(editor.getEditorInput());
if (isSelection) {
TextSelection selection = (TextSelection) editor.getSelectionProvider().getSelection();
int offset = selection.getOffset();
int length = selection.getLength();
doc.replace(offset, length, fmtText);
editor.selectAndReveal(offset, fmtText.length());
} else {
doc.set(fmtText);
}
} catch (Exception ex) {
MessageDialog.openError(window.getShell(), "Explain PostgreSQL formatter", ex.getMessage());
}
}
});
return Status.OK_STATUS;
}
};
job.setPriority(Job.SHORT);
job.schedule();
return null;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isHandled() {
return true;
}
}
Настройки плагина
Для создания панели настроек добавляем расширение org.eclipse.ui.preferencePages и создаем в нем свою страницу:

При создании класса поменяем Superclass на более удобный FieldEditorPreferencePage (описание) и добавим новый тип поля URLString с валидацией введенного URL :
код класса PreferencePage
public class PreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage {
@Override
public void init(IWorkbench workbench) {
}
@Override
protected void createFieldEditors() {
addField(new URLStringFieldEditor(
"API_URL",
"&API URL:",
getFieldEditorParent()
));
addField(new BooleanFieldEditor(
"USE_EXTERNAL",
"&Use external browser",
getFieldEditorParent()
));
}
}
public class URLStringFieldEditor extends StringFieldEditor {
private int validateStrategy = VALIDATE_ON_FOCUS_LOST;
public URLStringFieldEditor(String name, String labelText, Composite parent) {
super(name, labelText, parent);
setEmptyStringAllowed(false);
setValidateStrategy(validateStrategy);
setErrorMessage("Please input valid URL");
}
@Override
protected boolean doCheckState() {
String text= getTextControl().getText();
if (text != null && text.length() > 0) {
try {
new URL(text).openStream().close();
} catch (MalformedURLException e) {
return false;
} catch (IOException e) {
return false;
}
}
return true;
}
}
Анализ запроса
Для добавления функционала анализа запроса используем точки расширения org.eclipse.datatools.sqltools.plan.planService для Database Management и org.jkiss.dbeaver.sqlPlanView для DBeaver .
В отличие от Database Management, где весь процесс получения и обработки плана запроса отдается на сторону расширения, в DBeaver реализован свой механизм, в котором парсинг плана запроса выполняется строго в формате XML, а расширениям выдается готовый объект с одним полем Plan , остальные корневые поля плана удаляются (Planning, Planning Time, Triggers, Execution Time) :

Поэтому для получения полноценного плана мы добавили еще одну команду Explain Analyze вместе с Format SQL:

Сборка и публикация плагина
Для загрузки и установки плагинов необходимо объединить их в проекте Feature Project под общей лицензией. Для этого в меню File -> New -> Feature Project создаем новый проект и задаем перечень используемых плагинов, зависимости и лицензию. В список зависимостей надо включить все feature, которые должны быть установлены перед установкой нашего плагина.
Для нашего проекта необходимыми являются Chromium Intergration for Eclipse и Eclipse Data Tools Platform , DBeaver - опциональный:

Если создается несколько feature , то их можно объединить в категории, для этого в File -> New -> Other->Plug-in Development->Category Definition создаем category.xml с Explain PostgreSQL, включающей две feature :

Экспортируем наши features File -> Export -> Deployable featuresв каталог:

Указываем файл с категориями:

Создаем ключи, сертификаты и хранилище:
# создаем приватный ключ
openssl genpkey -aes-256-cbc -algorithm RSA -pkeyopt rsa_keygen_bits:4096 | openssl rsa -out private.pem
# создаем цепочку сертификатов
openssl req -key private.pem -new -x509 -days 365 -out chain.crt
# создаем архив PKCS12
openssl pkcs12 -export -in chain.crt -inkey private.pem -out store.p12 -name eclipse-plugin
# создаем java keystore
keytool -importkeystore -srckeystore store.p12 -srcstoretype PKCS12 -destkeystore store.jksДобавляем хранилище на вкладке JAR signing:

После завершения экспорта в каталоге /tmp/ru.tensor.explain.update будут созданы файлы content.jar , artifacts.jar и каталоги plugins и features , далее их необходимо опубликовать на своем веб-сайте и при создании страницы плагина на Eclipse Marketplace указать Update Site URL:

Также возможна установка из локального репозитория:

При работе с Eclipse в защищенной корпоративной среде может потребоваться установка сертификата в локальное хранилище, для этого запускаем команду, используя стандартный пароль "changeit", и перезапускаем IDE:
# для Eclipse
keytool -import -trustcacerts -alias root -file <файл с сертификатом> -keystore <путь к Eclipse>/plugins/org.eclipse.justj.openjdk.hotspot.jre.<версия>/jre/lib/security/cacerts
# для DBeaver на Mac
/Applications/DBeaver.app/Contents/Eclipse/jre/Contents/Home/bin/keytool -import -trustcacerts -alias root -file <файл с сертификатом> -keystore /Applications/DBEaver.app/Contents/Eclipse/jre/Contents/Home/lib/security/cacertsКод плагина опубликован под лицензией MIT.
