Создание интерпретатора языка SQL для объекта JTable библиотеки SWING
Введение
Идея создания интерпретатора языка SQL, возникла при разработке объектно - ориентированной системы планирования экспериментов. Данная система опирается на методы теории планирования эксперимента и математической статистики [1,2,3]. В системе выполняется всевозможная статистическая оценка экспериментальных данных, рассчитываются среднее значение, дисперсия, математическое ожидание, максимальное и минимальное значения, табуляция и построение графика полиномиальной функции, проверка адекватности экспериментальной модели, оптимизация параметров и т.д. Промежуточное хранение и обработка данных осуществляется в объекте JTable. Безусловно, довольно логичным вариантом для этого является сохранение таблицы на жестком диске, используя любой JDBC драйвер, и манипуляция ее данными с помощью языка SQL. Однако в нашем случае, например, возникает следующая ситуация, когда из сотен тысяч записей, нам необходимо, говоря терминологией языка SQL выполнить группировку данных с вычислением агрегатной функции, получив итоговую таблицу, состоящую из десятков или сотен записей, которую нужно хранить на жестком диске. На начальном этапе, был создан специальный класс, который реализовывал самые простые агрегатные функции. Однако дальнейшего развития такой подход не получил. Это, по всей видимости, связано с тем, что по мере усложнения алгоритмов обработки данных, наиболее четкое представление о результирующей выборке, можно получить используя, терминологию и конструкции языка SQL.
Постановка задачи
Анализ различных источников показал [4,5], что в настоящий момент для Java существует множество возможностей реализации языка SQL. Всех их отличает то, что они предназначены для работы с базами данных и таблицами, расположенными в файловых структурах (в качестве примера можно взять hSQl – hsql.sourceforge.net база данных полностью написанная на Java). Ни один из них не поддерживает возможность работы "напрямую" с компонентой представления данных JTable). Поэтому задача разработки интерпретатора SQL который бы на прямую работал с компонентой JTable является актуальной.
Также, одной из задач, которая стояла при разработке интерпретатора, являлось сравнение времени выполнения различных типов запросов для различных физических реализаций: dbf – таблицы, сервер баз данных - MySQL v.4. Так как в случае, если бы по быстродействию интерпретатор значительно уступал им, то целесообразность дальнейшей разработки была бы под большим вопросом. Забегая вперед, следует отметить, что результаты превзошли все наши ожидания.
Особенности реализации
Одной из основных проблем, при разработке интерпретатора является проблема контроля типа данных столбцов.
В таблицах баз данных существуют специальные механизмы для поддержания доменной целостности. Поэтому, на начальном этапе разработки, исходя из области применения системы, мы решили в качестве начальных условий принять, что все столбцы будут иметь тип - числовое с плавающей точкой, двойной точности. Тип столбцов, или даже конкретных ячеек, не участвующих в обработке данных, не важен, и может быть любым.
Ввод данных на экран осуществляется следующим образом – все таблицы располагаются в объектах, которые отображаются через компонент JDesktopPane, как дочерние окна - JInternalFrame, основная функция интерпретатора принимает в качестве параметра их список. Выбор из списка окон, содержащих таблицы производиться по следующему принципу: каждое окно, которое содержит таблицу, с которой предполагается работа интерпретатора должно поддерживать специальный интерфейс – QlTableProtocolI, если окно не поддерживает этот интерфейс, то оно не участвует в выполнении запроса. Интерфейс QlTableProtocolI содержит один метод – GetTable, который возвращает объект QlTable- контейнерный класс для имени таблицы и ее модели (DefaultTableModel) – хранилища данных таблицы. Так как имя таблицы, в отличие от баз данных, может содержать служебные символы языка SQL, в запросе оно должно быть заключено в двойные кавычки, тоже самое касается и имен столбцов. Со структурой самой таблицы проблем не возникает, поскольку она однозначно определяется объектом DefaultTableModel со стороны библиотеки SWING.
Обработка QL запроса состоит из нескольких шагов. На первом этапе происходит разбор запроса на отдельные части (поля для выборки, список таблиц и др.). Далее проводиться синтаксический анализ каждой части запроса – составляется список полей для выборки, список таблиц, задействованных в запросе, полей группировки и сортировки, предикаты фильтрации where и from разбираются в двоичные деревья (детально разбор выражения в двоичное дерево описан ниже).
Далее организуется цикл по всем строкам таблиц, участвующих в запросе. Каждая строка проверяется на соответствие условию, указанному в предикате фильтрации WHERE. Если строка соответствует этому условию, то она передается объекту - группировщику. Если в запросе присутствовал оператор GROUP BY, то группировщик либо причисляет строку к существующей группе, либо создаёт новую группы и заносит объект туда. Если GROUP BY не было в запросе, то группировщик заносит строку в группу по умолчанию.
После работы цикла для каждой группы вычисляются агрегатные функции, и из групп формируется общая таблица. Эта таблица, после фильтрации по предикату HAVING возвращается как результат.
Для разбора выражения в дерево были созданы следующие классы: EvaParser – организует при разработке интерпретатора систему разбора, управляет объектами EvaParseLevel. EvaParseLevel – непосредственно проводит разбор в дерево для каждого уровня приоритета операции, содержит список объектов EvaOp. EvaOp – объект для хранения конкретной операции и ее свойств. EvaExpTree – управляет уже разобранным выражением. EvaTreeNode-объект, отвечающий за узел дерева – содержит код операции и ссылки на левый и правый подузлы, либо конечные данные или ссылку на столбец таблицы.
Разбор проходит следующим образом. Вначале, при инициализации объекта EvaParser составляется связный список из объектов EvaParseLevel. Объектам в списке присваиваются текстовые отображения конкретных арифметических, или логических операций в порядке убывания приоритета, если две операции имеют одинаковый приоритет, они присваиваются одному объекту EvaParseLevel. Последнему элементу присваивается нулевая операция, которая в дальнейшем будет отвечать за непосредственное обращение к данным.
После создания списка вызывается метод parse объекта, находящегося в начале списка – отвечающего за операцию с самым высоким приоритетом. Результатом вызова является объект EvaTreeNode, который является корнем дерева.
При вызове метода parse происходит поверка - является ли данный объект последним в списке. Если это так, то следующий элемент(token) выражения не может быть операцией, и представляет данные. Данные пакуются в объект EvaTreeNode и возвращаются, а указатель текущего элемента выражения переключается на следующий элемент. Если элемент не является последним в списке, то метод создает объект EvaTreeNode. Затем вызывает метод parse следующего в списке объекта EvaParseLevel. Объект, полученный после вызова, присваивается указателю на левый подузел ранее созданного объекта EvaTreeNode. На данном этапе указатель текущего элемента должен указывать либо на конец выражения, либо на операцию.
Если указатель указывает на конец выражения, то разбор прекращается, в качестве результата возвращается левый подузел созданного узла дерева. Если же указатель указывает на операцию, то происходить проверка принадлежности этой операции текущему уровню. Если операция принадлежит текущему узлу, то вызывается метод parse следующего в списке объекта, результат которого присваивается правому подузлу текущего узла. В противном случае возвращается левый подузел текущего узла.
Далее, если выражение не кончилось, создаются новый объект TreeNode, левый узел которого указывает на предыдущий узел. Правый узел присваивается результату вызова метода parse следующего объекта в списке. Так повторяется до тех пор, пока выражение не кончиться.
Рисунок 1. Пример дерева
Методы испытаний
Испытания производительности пакета проводились следующим образом. Была создана таблица из четырех столбцов и 100000 строк случайных данных с плавающей точкой двойной точности. Таблица была создана в трех форматах: в формате dBaseIII(dbf), в виде заполненной таблицы JTable, в СУБД MySql. Для каждого источника данных было проведено четыре теста. Каждый тест повторялся по пять раз.
Тесты проводились с помощью следующих запросов.
Запрос | Размер выборки(строк) |
select * from "Func1" | 100000 |
select * from "Func1" where "X">0.5 | 49968 |
select * from "Func1" where "X">0.5 and "F(X)"<0.7 | 34885 |
select * from "Func1" where "X">0.5 and "F(X)"<0.7 and "FI(X)"<0.2 | 7011 |
При обращении к dbf и MySql запросы преобразовывались к стандартному sql виду – опускались кавычки в именах таблиц скобки в названиях полей заменялись на “_”. Ниже приведены результаты тестов на компьютере с процессором Athlon 2500+ (Barton), 512mb RAM, nForce2 ultra.
Результаты эксперимента и их обсуждение
Рисунок 2. Диаграмма результатов эксперимента
Первый был тест в направлен на выявление скорости передачи данных, в нем выполнялся простой запрос на выборку всех данных таблицы. Очевидно, что отставание dbf и MySql вызвано необходимостью читать данные с жесткого диска, но в большей степени из-за передачи их программе через интерфейс JDBC. Но уже на этом тесте видно, пока еще не значительное, превосходство MySql над dbf. Хочется отметить, что СУБД MySql изначально разрабатывалась с целью обеспечения максимального быстродействия. Скорее всего здесь сказывается более грамотная стратегия обращения MySql к дисковым ресурсам.
Во втором тесте размер выборки уже в два раза меньше, так как введено простое условие, которое все же замедляет обработку каждой сроки.
В этом тесте MySql уже серьезно опережает dbf, который несмотря на то, что объем передаваемых стал в два раза меньше выполнил этот запрос не намного быстрее, чем предыдущий.
Довольно интересны результаты четвертого теста. Здесь размер выборки уже достаточно мал, и не должен серьезно замедлять работу. В данном случае время тратиться в основном на непосредственную обработку данных. Отчетливо становится видно отставание dbf, он справился с обработкой этого запроса лишь в 3.7 раза быстрее, чем первого, хотя объем передаваемых данных сократился в 14.3 раза.
Выводы
- На основании полученных результатов тестирования, установлено, что QL пригоден для обработки больших объемов числовых данных.
- По скорости выполнения запросов QL сопоставим и с MySql, а из-за отсутствия необходимости передачи данных через мост JDBC и чтения с диска даже показывает более высокое быстродействие.
- Низкая производительность при обработке dfb-таблиц в последних трех тестах, на наш взгляд, вызвано неэффективной работой с диском, и, скорее всего, неэффективной обработкой и фильтрацией строк.
Используемые источники
- Адлер Ю.П., Маркова Е.В., Грановский М.Б. Оптимальное планирование эксперимента. М. Наука 1975г. 320.
- Зедгинидзе И.Г. Планирование эксперимента для исследовании многокомпонентных систем. М. Наука – 1976, 390.
- Применение математических методов для исследования многокомпонентных систем. М., Металлургия, 1974, 176с. с ил.
- Глушаков С.В.Программирование на Java2. Харьков «Фолио», Москва «АСТ » 2001-536с
- www.sun.com - Web – сайт фирмы Sun, разработчика языка Java2
- www.javable.com - Информационный портал, посвященный различным аспекта разработки и программирования на основе технологии Java2
CREATING EMBEDED SQL INTERPRETER FOR JTABLE OBJECT FROM “SWING” LIBRARY Ebeded Sql interpreter for Jtable -new effective tool for experimental data proccessing, written on Java. This solution helps evading expenses of data transfer to/from DBMS and hardcodec data processing. This tool is much faster than traditional DBMS access.
Окончил технологический факультет ДГТУ (1993).
доцент кафедры «Информатика» ДГТУ,
кандидат технических наук (1996).
ДЕМЬЯНЕНКО Александр Григорьевич
(р.1986),студент факультета «АИ» ДГТУ
ДГТУ г.Ростов-на-Дону
Январь 2005г.