Плагин для анализа планов PostgreSQL в Eclipse и DBeaver, и его разработка

Explain PostgreSQL plugin

Explain PostgreSQL plugin

Для пользователей explain.tensor.ru — нашего сервиса визуализации PostgreSQL-планов, в дополнение к плагину Jetbrains мы создали еще один — с возможностью форматировать запросы и анализировать планы в Eclipse IDE и DBeaver.

Установка плагина

В Eclipse установку удобнее выполнить через Marketplace, при этом дополнительно установятся необходимые компоненты из категории Data Tools Platform и Chromium Integration. Если в Eclipse присутствует плагин DBeaver, то к нему также добавятся функции форматирования и анализа запросов.

Установка плагина из marketplace

Установка плагина из marketplace

В отдельном приложении DBeaver надо добавить URL update-сайта с плагином. Для этого необходимо скачать файл:

curl -sLO https://explain.tensor.ru/downloads/plugins/eclipse/bookmarks.xml

импортировать его в меню Window -> Preferences -> General -> Install/Update -> Available Software Sites:

Import software sites

Import software sites

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

Установка плагина с сайта

Установка плагина с сайта

Установка возможна также из командной строки, текст команд для разных систем можно посмотреть на сайте explain.tensor.ru.

Форматирование запроса

Форматирование доступно в перспективах Database Development и DBeaver в контекстном меню редактора SQL:

Форматирование в Database Development

Форматирование в Database Development

Форматирование в DBeaver

Форматирование в DBeaver

Анализ запроса

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

анализ в Database Development

анализ в Database Development

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

анализ в DBeaver

анализ в DBeaver

Настройка плагина

При необходимости можно поменять сайт настройках 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 — добавляем нашу панель рядом с панелью результатов запроса:

добавление панели в перспективу редактора SQL

добавление панели в перспективу редактора 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) :

парсинг плана в DBeaver

парсинг плана в DBeaver

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

получение плана в DBeaver

получение плана в DBeaver

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

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

Для нашего проекта необходимыми являются Chromium Intergration for Eclipse и Eclipse Data Tools Platform, DBeaver — опциональный:

optional dependency

optional dependency

Если создается несколько 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:

JAR Signing

JAR Signing

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

публикация на marketplace

публикация на marketplace

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

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

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

При работе с Eclipse в защищенной корпоративной среде может потребоваться установка сертификата в локальное хранилище, для этого запускаем команду, используя стандартный пароль «changeit», и перезапускаем IDE:

keytool -import -trustcacerts -alias root -file <файл с сертификатом> -keystore <путь к Eclipse>/plugins/org.eclipse.justj.openjdk.hotspot.jre.<версия>/jre/lib/security/cacerts

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

© Habrahabr.ru