Для пользователей 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.