From 3a151fce45acd0a1f7ec077b8320b90b5fb34968 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Mon, 21 Sep 2015 00:49:14 +0100 Subject: Migrate SQL script parser and add support for executing a script on a connection --- libdbpp/Jamfile.jam | 11 ++- libdbpp/connection.cpp | 8 ++ libdbpp/connection.h | 2 + libdbpp/sqlParse.h | 36 +++++++++ libdbpp/sqlParse.ll | 153 +++++++++++++++++++++++++++++++++++ libdbpp/unittests/Jamfile.jam | 2 - libdbpp/unittests/parseTest.sql | 12 +++ libdbpp/unittests/testConnection.cpp | 22 ++++- 8 files changed, 242 insertions(+), 4 deletions(-) create mode 100644 libdbpp/sqlParse.h create mode 100644 libdbpp/sqlParse.ll create mode 100644 libdbpp/unittests/parseTest.sql diff --git a/libdbpp/Jamfile.jam b/libdbpp/Jamfile.jam index e935937..3eedb10 100644 --- a/libdbpp/Jamfile.jam +++ b/libdbpp/Jamfile.jam @@ -1,21 +1,30 @@ import package ; +import lex ; + alias glibmm : : : : "`pkg-config --cflags glibmm-2.4`" "`pkg-config --libs glibmm-2.4`" ; lib boost_date_time : : boost_date_time ; +lib boost_filesystem ; +lib boost_system ; lib adhocutil : : : : /usr/include/adhocutil ; lib dbppcore : - [ glob *.cpp ] : + [ glob *.cpp *.ll ] : glibmm adhocutil + boost_system + boost_filesystem -fvisibility=hidden release:-flto + . : : . glibmm boost_date_time + boost_system + boost_filesystem ; build-project unittests ; diff --git a/libdbpp/connection.cpp b/libdbpp/connection.cpp index 2d93ce3..2640148 100644 --- a/libdbpp/connection.cpp +++ b/libdbpp/connection.cpp @@ -2,6 +2,7 @@ #include "modifycommand.h" #include #include +#include DB::Connection::~Connection() { @@ -21,6 +22,13 @@ DB::Connection::execute(const std::string & sql) const } } +void +DB::Connection::executeScript(std::istream & f, const boost::filesystem::path & s) const +{ + DB::SqlParse p(f, s, this); + while (p.yylex()) ; +} + void DB::Connection::savepoint(const std::string & sp) const { diff --git a/libdbpp/connection.h b/libdbpp/connection.h index 9c6b46e..2e17490 100644 --- a/libdbpp/connection.h +++ b/libdbpp/connection.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace DB { class SelectCommand; @@ -35,6 +36,7 @@ namespace DB { virtual BulkUpdateStyle bulkUpdateStyle() const = 0; virtual void execute(const std::string & sql) const; + virtual void executeScript(std::istream & f, const boost::filesystem::path & s) const; virtual SelectCommand * newSelectCommand(const std::string & sql) const = 0; virtual ModifyCommand * newModifyCommand(const std::string & sql) const = 0; diff --git a/libdbpp/sqlParse.h b/libdbpp/sqlParse.h new file mode 100644 index 0000000..ad001bb --- /dev/null +++ b/libdbpp/sqlParse.h @@ -0,0 +1,36 @@ +#ifndef DB_SQLPARSE_H +#define DB_SQLPARSE_H + +#include +#include +#include "connection.h" +#include +#ifndef yyFlexLexer +#define yyFlexLexer sqlBaseFlexLexer +#include +#endif + +namespace DB { + +class SqlParse : public yyFlexLexer { + public: + SqlParse(std::istream &, const boost::filesystem::path &, const Connection *); + int yylex() override; + + void Comment(const std::string &) const; + void Statement(const std::string &) const; + + protected: + void LexerError(const char *) override; + + private: + const DB::Connection * conn; + const boost::filesystem::path scriptDir; + std::string comment; + std::string statement; +}; + +} + +#endif + diff --git a/libdbpp/sqlParse.ll b/libdbpp/sqlParse.ll new file mode 100644 index 0000000..253b91f --- /dev/null +++ b/libdbpp/sqlParse.ll @@ -0,0 +1,153 @@ +%option batch +%option c++ +%option noyywrap +%option 8bit +%option stack +%option yylineno +%option yyclass="DB::SqlParse" +%option prefix="sqlBase" + +%{ +#include +#include "sqlParse.h" +#pragma GCC diagnostic ignored "-Wsign-compare" +%} + +space [ \t\n\r\f] +non_newline [^\r\n] +mcomment_start "/*" +mcomment_stop "*/" +comment ("--"{non_newline}*) +other . +term ; +any ({other}|{space}) +quote ' +quote_apos '' +dolq_start [A-Za-z\200-\377_] +dolq_cont [A-Za-z\200-\377_0-9] +dollarquote \$({dolq_start}{dolq_cont}*)?\$ +scriptdir "$SCRIPTDIR" + +%x COMMENT +%x STATEMENT +%x QUOTE +%x DOLLARQUOTE + +%% + +{mcomment_start} { + comment += YYText(); + yy_push_state(COMMENT); +} + +{mcomment_stop} { + comment += YYText(); + Comment(comment); + comment.clear(); + yy_pop_state(); +} + +{any} { + comment += YYText(); +} + +<> { + throw std::runtime_error("Unterminated comment"); +} + +{comment} { + Comment(YYText()); +} + +{other} { + statement += YYText(); + yy_push_state(STATEMENT); +} + +{quote} { + statement += YYText(); + yy_push_state(QUOTE); +} + +{dollarquote} { + statement += YYText(); + yy_push_state(DOLLARQUOTE); +} + +{quote} { + statement += YYText(); + yy_pop_state(); +} + +{scriptdir} { + statement += scriptDir.string(); +} + +{quote_apos} { + statement += YYText(); +} + +{any} { + statement += YYText(); +} + +{dollarquote} { + statement += YYText(); + yy_pop_state(); +} + +<> { + throw std::runtime_error("Unterminated dollar quoted string"); +} + +{any} { + statement += YYText(); +} + +<> { + throw std::runtime_error("Unterminated quoted string"); +} + +{term} { + Statement(statement); + statement.clear(); + yy_pop_state(); +} + +{any} { + statement += YYText(); +} + +<*>[ \t\r\n\f] { +} + +%% + +namespace DB { + + SqlParse::SqlParse(std::istream & f, const boost::filesystem::path & s, const Connection * c) : + yyFlexLexer(&f, NULL), + conn(c), + scriptDir(s) + { + } + + void + SqlParse::LexerError(const char * msg) + { + throw std::runtime_error(msg); + } + + void + SqlParse::Comment(const std::string &) const + { + } + + void + SqlParse::Statement(const std::string & text) const + { + conn->execute(text); + } + +} + diff --git a/libdbpp/unittests/Jamfile.jam b/libdbpp/unittests/Jamfile.jam index ef19211..cb44781 100644 --- a/libdbpp/unittests/Jamfile.jam +++ b/libdbpp/unittests/Jamfile.jam @@ -3,8 +3,6 @@ import testing ; path-constant me : . ; lib boost_utf : : boost_unit_test_framework ; -lib boost_filesystem ; -lib boost_system ; run testConnection.cpp diff --git a/libdbpp/unittests/parseTest.sql b/libdbpp/unittests/parseTest.sql new file mode 100644 index 0000000..56eac57 --- /dev/null +++ b/libdbpp/unittests/parseTest.sql @@ -0,0 +1,12 @@ +CREATE TABLE name ( + t text, + i int, + primary key(i) + ); +-- Single line comment +INSERT INTO name(t, i) VALUES('string', 3); +/* + Multi line + comment + */ + diff --git a/libdbpp/unittests/testConnection.cpp b/libdbpp/unittests/testConnection.cpp index 3472dde..a707f2f 100644 --- a/libdbpp/unittests/testConnection.cpp +++ b/libdbpp/unittests/testConnection.cpp @@ -3,6 +3,9 @@ #include #include +#include +#include +#include class MockDb : public DB::Connection { public: @@ -20,13 +23,17 @@ class MockDb : public DB::Connection { DB::BulkDeleteStyle bulkDeleteStyle() const { return DB::BulkDeleteUsingUsing; } DB::BulkUpdateStyle bulkUpdateStyle() const { return DB::BulkUpdateUsingJoin; } - void execute(const std::string &) const {} + void execute(const std::string & sql) const { + executed.push_back(sql); + } DB::SelectCommand * newSelectCommand(const std::string &) const { return nullptr; } DB::ModifyCommand * newModifyCommand(const std::string &) const { return nullptr; } void beginBulkUpload(const char *, const char *) const {} void endBulkUpload(const char *) const {} size_t bulkUploadData(const char *, size_t) const {return 0;} + + mutable std::vector executed; }; FACTORY(MockDb, DB::ConnectionFactory); @@ -55,3 +62,16 @@ BOOST_AUTO_TEST_CASE( resolve ) BOOST_REQUIRE_THROW(DB::ConnectionFactory::create("otherdb", "doesn't matter"), AdHoc::LoadLibraryException); } +BOOST_AUTO_TEST_CASE( parse ) +{ + auto mock = DB::ConnectionFactory::create("MockDb", "doesn't matter"); + std::fstream s((rootDir / "parseTest.sql").string()); + BOOST_REQUIRE(s.good()); + mock->executeScript(s, rootDir); + MockDb * mockdb = dynamic_cast(mock); + BOOST_REQUIRE(mockdb); + BOOST_REQUIRE_EQUAL(2, mockdb->executed.size()); + BOOST_REQUIRE_EQUAL("INSERT INTO name(t, i) VALUES('string', 3)", mockdb->executed[1]); + delete mock; +} + -- cgit v1.2.3