Разбор SQL-запросов в C ++ с использованием Boost.Spirit

Я создал механизм базы данных, в котором я могу создавать и изменять таблицы и добавлять их в базу данных. Для анализа запросов SQL я реализовал библиотеку Boost.Spirit с использованием формы EBNF. Я правильно настроил парсер и он успешно разбирает каждое правило.

Моя проблема в том, что теперь я понятия не имею, как интегрировать их. Синтаксический анализатор Boost.Spirit только проверяет правильность ввода, однако на самом деле он мне нужен делать что-то. Я искал семантические действия, но они, кажется, не справляются с тем, что я ищу.

Например, если у меня есть запрос, такой как:
new_table <- SELECT (id < 5) old_table;

Я хочу, чтобы он подтвердил ввод, используя правила, а затем вызовите функцию
Table Database::Select(Table t , Condition c){ ... }
и передать токены в качестве аргументов.

Как мне интегрировать синтаксический анализатор?

5

Решение

Примечание. Я решил изобразить здесь примерную грамматику в демонстрационных целях, поскольку ваш вопрос не отражает ваш. Используя подход, рекомендованный здесь, не должно быть сложно написать функцию для выполнения ваших запросов после анализа.

Я бы действительно предложил построить дерево разбора.

Я бы порекомендовал распространение атрибутов, а не семантические действия. Смотрите, например

Правила распространения атрибутов очень гибки в духе. Открытые типы атрибутов по умолчанию хорошо документированы правильно с документацией каждого парсера

Например. —qi::char_ приведет к boost::optional<char> а также qi::double_ | qi::int_ приведет к boost::variant<double, int>,

Возможно, вы захотите накопить проанализированные элементы в типе данных AST вашего собственного изобретения, например:

struct SelectStatement
{
std::vector<std::string> columns, fromtables;
std::string whereclause; // TODO model as a vector<WhereCondition> :)

friend std::ostream& operator<<(std::ostream& os, SelectStatement const& ss)
{
return os << "SELECT [" << ss.columns.size() << " columns] from [" << ss.fromtables.size() << " tables]\nWHERE " + ss.whereclause;
}
};

Вы можете адаптировать это к механизму распространения атрибутов Spirits, адаптировав структуру как последовательность Fusion:

BOOST_FUSION_ADAPT_STRUCT(SelectStatement,
(std::vector<std::string>, columns)
(std::vector<std::string>, fromtables)
(std::string, whereclause)
)

Теперь вы можете разобрать следующее правило в этот тип:

sqlident = lexeme [ alpha >> *alnum ]; // table or column name

columns  = no_case [ "select" ] >> (sqlident % ',');
tables   = no_case [ "from" ]   >> (sqlident % ',');

start    = columns >> tables
>> no_case [ "where" ]
>> lexeme [ +(char_ - ';') ]
>> ';';

Вы можете увидеть это код работает Вот: http://liveworkspace.org/code/0b525234dbce22cbd8becd69f84065c1

Полный демонстрационный код:

// #define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>

namespace qi    = boost::spirit::qi;

struct SelectStatement
{
std::vector<std::string> columns, fromtables;
std::string whereclause; // TODO model as a vector<WhereCondition> :)

friend std::ostream& operator<<(std::ostream& os, SelectStatement const& ss)
{
return os << "SELECT [" << ss.columns.size() << " columns] from [" << ss.fromtables.size() << " tables]\nWHERE " + ss.whereclause;
}
};

BOOST_FUSION_ADAPT_STRUCT(SelectStatement,
(std::vector<std::string>, columns)
(std::vector<std::string>, fromtables)
(std::string, whereclause)
)

template <typename It, typename Skipper = qi::space_type>
struct parser : qi::grammar<It, SelectStatement(), Skipper>
{
parser() : parser::base_type(start)
{
using namespace qi;

sqlident = lexeme [ alpha >> *alnum ]; // table or column name

columns  = no_case [ "select" ] >> (sqlident % ',');
tables   = no_case [ "from" ]   >> (sqlident % ',');

start    = columns >> tables
>> no_case [ "where" ]
>> lexeme [ +(char_ - ';') ]
>> ';';

BOOST_SPIRIT_DEBUG_NODE(start);
BOOST_SPIRIT_DEBUG_NODE(sqlident);
BOOST_SPIRIT_DEBUG_NODE(columns);
BOOST_SPIRIT_DEBUG_NODE(tables);
}

private:
qi::rule<It, std::string()             , Skipper> sqlident;
qi::rule<It, std::vector<std::string>(), Skipper> columns  , tables;
qi::rule<It, SelectStatement()         , Skipper> start;
};

template <typename C, typename Skipper>
bool doParse(const C& input, const Skipper& skipper)
{
auto f(std::begin(input)), l(std::end(input));

parser<decltype(f), Skipper> p;
SelectStatement query;

try
{
bool ok = qi::phrase_parse(f,l,p,skipper,query);
if (ok)
{
std::cout << "parse success\n";
std::cout << "query: " << query << "\n";
}
else      std::cerr << "parse failed: '" << std::string(f,l) << "'\n";

if (f!=l) std::cerr << "trailing unparsed: '" << std::string(f,l) << "'\n";
return ok;
} catch(const qi::expectation_failure<decltype(f)>& e)
{
std::string frag(e.first, e.last);
std::cerr << e.what() << "'" << frag << "'\n";
}

return false;
}

int main()
{
const std::string input = "select id, name, price from books, authors where books.author_id = authors.id;";
bool ok = doParse(input, qi::space);

return ok? 0 : 255;
}

Распечатает вывод:

parse success
query: SELECT [3 columns] from [2 tables]
WHERE books.author_id = authors.id
14

Другие решения

Других решений пока нет …