diff --git a/.vscode/settings.json b/.vscode/settings.json index 91f1f52..cb70f56 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,7 +5,8 @@ "*.inc": "c", "vector": "cpp", "string.h": "c", - "libpq-fe.h": "c" + "libpq-fe.h": "c", + "new": "cpp" }, "cmake.configureArgs": [ diff --git a/CMakeLists.txt b/CMakeLists.txt index 5371c12..ba6d5e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,18 +98,21 @@ endif() if(CROSS_COMPILE STREQUAL "Windows") #add_compile_definitions(_CRT_SECURE_NO_DEPRECATE=1 NDEBUG WIN32 _WINDOWS __WINDOWS__ __WIN95__ __WIN32__ WINVER=0x0500 STRICT __WXMSW__ WXUSINGDLL wxUSE_UNICODE=1 PG_SSL) add_compile_definitions(NDEBUG WIN32 WXPRECOMP WINVER=0x0600 STRICT wxUSE_UNICODE=1 PG_SSL) - set(WXWIN /home/sergey/k/wxMSW-3.2.2 ) - set(PGDIR /home/sergey/k/pg10 ) - set(PGBUILD /home/sergey/k ) + ##set(WXWIN /home/sergey/k/wxMSW-3.2.2 ) + ##set(PGDIR /home/sergey/k/pg17 ) + ##set(PGBUILD /home/sergey/k ) #set(/home/sergey/k/openssl ) #set(PROJECTDIR /home/sergey/k/openssl ) include_directories(${WXWIN}/include ${PGDIR}/include ${PGDIR}/include/server ) include_directories(${PGBUILD}/libxml2/include ${PGBUILD}/libxslt/include ${PGBUILD}/iconv/include ) # include_directories(${PGBUILD}/openssl/include # include_directories( ./include/libssh2 ./include/libssh2/Win32 ) +else() + add_subdirectory(tests) endif() + set(CPACK_PROJECT_NAME ${PROJECT_NAME}) set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) diff --git a/README.md b/README.md index 11b63c3..a1e0dd9 100644 --- a/README.md +++ b/README.md @@ -402,3 +402,13 @@ English version issue #18 Выделеный текст в окне подсказки при нажатии правой кнопки используется как строка поиска. * Исправлен запрос получения информации о серверах и теперь он работает быстрее. Добавлена информация в строку статуса о времени работы запроса. + +21.11.2025 + + - Контекстная справка по функциям доросла до гипертекстовой навигации по функция, таблицам, триггерам. + Выделенное выражение будет проверятся по именам объектов БД и если такие будут найдены то + скрипт создания будет выдаваться как справка по объекту. Скрипт анализируется на вызовы функций, + имена таблиц и представлений, которые заменяются ссылки. Анализ не точный поэтому возможны + не корректные ссылки. Переход назад по правой кнопке мыши. + + - Добавлен каталог для тестов, но они будут работать только при компиляции в linux. diff --git a/ctl/ctlSQLBox.cpp b/ctl/ctlSQLBox.cpp index 71ed9d5..b729c9c 100644 --- a/ctl/ctlSQLBox.cpp +++ b/ctl/ctlSQLBox.cpp @@ -643,8 +643,11 @@ void ctlSQLBox::OnFuncHelp(wxCommandEvent& ev) { wxPoint p = ClientToScreen( PointFromPosition(pos)); wxString current = GetSelectedText(); wxString key = ""; + fh->setDbConn(m_database); if (!current.IsEmpty()) + { key = current; + } else { wxChar ch; wxString tmp; @@ -660,12 +663,12 @@ void ctlSQLBox::OnFuncHelp(wxCommandEvent& ev) { } delete m_PopupHelp; wxSize rr(450, 370); - m_PopupHelp = new popuphelp(this->GetParent(), key.Lower(), fh,p,rr); + m_PopupHelp = new popuphelp(this->GetParent(), key, fh,p,rr); if (m_PopupHelp && m_PopupHelp->IsValid() && rr != m_PopupHelp->GetSizePopup()) { // recreate with new size rr = m_PopupHelp->GetSizePopup(); delete m_PopupHelp; - m_PopupHelp = new popuphelp(this->GetParent(), key.Lower(), fh, p, rr); + m_PopupHelp = new popuphelp(this->GetParent(), key, fh, p, rr); } if (m_PopupHelp && m_PopupHelp->IsValid()) { @@ -1766,7 +1769,7 @@ void ctlSQLBox::OnMarginClick(wxStyledTextEvent &event) event.Skip(); } -wxString ctlSQLBox::TextToHtml(int start, int end,bool isAddNewLine) { +wxString ctlSQLBox::TextToHtml(int start, int end,bool isAddNewLine, const std::vector &listobj) { wxColor frColor[40]; wxString str; wxColour frc = settings->GetSQLBoxColourForeground(); @@ -1807,6 +1810,12 @@ wxString ctlSQLBox::TextToHtml(int start, int end,bool isAddNewLine) { //if (isAddNewLine) newline = L"⤶
"; //if (isAddNewLine) newline = L"
\x0b78"; int lenstr = selText.Length(); + int IndexObj=0; + int pos=999999999; + wxString obj; + int lenobj; + if (listobj.size()>0) {pos=listobj[IndexObj].startIndex; obj=listobj[IndexObj].table;lenobj=obj.Len();} + wxString lstr; while (k') { str+=">"; startp = startp + l; k++; continue; }; - if (c == '&') { str+="&"; startp = startp + l; k++; continue; }; - if (s > 0) for (int tt = 0; tt < s; tt++) str += wxT(" "); - else str += c; + if (c == '<') { lstr+="<"; startp = startp + l; k++; continue; }; + if (c == '>') { lstr+=">"; startp = startp + l; k++; continue; }; + if (c == '&') { lstr+="&"; startp = startp + l; k++; continue; }; + if (s > 0) for (int tt = 0; tt < s; tt++) lstr += " "; + else lstr += c; startp = startp + l; k++; + if ((k-1)>=pos) { + lenobj--; + if (lenobj==0) { + wxString s,n; + make_identifier(obj,s,n,true); + if (!s.IsEmpty()) obj=s+'.'+n; else obj=n; + lstr=wxString::Format("%s", obj, lstr); + IndexObj++; + if (listobj.size()>IndexObj) { pos=listobj[IndexObj].startIndex; obj=listobj[IndexObj].table; lenobj=obj.Len();} + else pos=999999999; + } else + continue; // link not ready + } + str += lstr; + lstr=""; } str = str + wxT(""); return str; diff --git a/include/ctl/ctlSQLBox.h b/include/ctl/ctlSQLBox.h index 680c295..070d140 100644 --- a/include/ctl/ctlSQLBox.h +++ b/include/ctl/ctlSQLBox.h @@ -53,7 +53,7 @@ public: void Create(wxWindow *parent, wxWindowID id = -1, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = 0); void HighlightBrace(int start, int len,int indicator); void SetDatabase(pgConn *db); - wxString TextToHtml(int start, int end, bool isAddNewLine=false); + wxString TextToHtml(int start, int end, bool isAddNewLine=false, const std::vector &listobj = {}); void Copy(); void OnKeyDown(wxKeyEvent &event); void OnAutoComplete(wxCommandEvent &event); diff --git a/include/precomp.h b/include/precomp.h index 3d791c1..556c08d 100644 --- a/include/precomp.h +++ b/include/precomp.h @@ -216,6 +216,7 @@ #include "schema/gpExtTable.h" #include "schema/gpPartition.h" #include "schema/gpResQueue.h" +#include "schema/pgPartition.h" #include "slony/dlgRepCluster.h" #include "slony/dlgRepListen.h" diff --git a/include/utils/FormatterSQL.h b/include/utils/FormatterSQL.h index e3046c7..7ca9c5f 100644 --- a/include/utils/FormatterSQL.h +++ b/include/utils/FormatterSQL.h @@ -77,6 +77,7 @@ namespace FSQL { { "left", 4, new_line_align_next }, { "full", 4, new_line_align_next }, { "then", 4, none}, + { "loop", 4, none}, // for plpgsql { "into", 4, none}, { "last", 4, none}, { "next", 4, none}, @@ -86,6 +87,9 @@ namespace FSQL { { "sets", 4, none}, { "where", 5, new_line_align_no_pad | end_from}, { "outer", 5, none}, + { "grant", 5, none}, + { "begin", 5, none}, //plpgsql + { "while", 5, none}, //plpgsql { "union", 5, new_line_align_no_pad | end_from}, { "order", 5, new_line_align_no_pad | end_from}, { "limit", 5, new_line_align_no_pad | end_from}, @@ -106,9 +110,11 @@ namespace FSQL { { "except", 6, new_line_align_no_pad | end_from}, { "offset", 6, none | end_from}, { "cursor", 6, none}, + { "create", 6, none}, { "nothing", 7, none}, { "lateral", 7, none}, { "between", 7, none}, + { "comment", 7, none}, { "nothing", 7, none}, { "default", 7, none}, @@ -149,11 +155,13 @@ namespace FSQL { FormatterSQL(const wxString& sqlsrc) { sql = sqlsrc; } + std::vector ParsePLpgsql(); void Formating(wxDC& d, wxRect re, bool isTest = false); // draw wxString Formating(wxRect re); // // wxString BuildAutoComplite(int startIndex, int level); wxString GetListTable(int cursorPos); + wxString GetListTable(const std::vector &list); wxString GetColsList(wxString what, wxString& listfieldOut, wxString& nameTableOut); /// /// Возращает количество таблиц слева от курсора и заполняет их имена и псевдонимы. @@ -166,11 +174,13 @@ namespace FSQL { void SetSql(const wxString& sqlsrc) { sql = sqlsrc; lastposition = 0; } int GetIndexItemNextSqlPosition(int sqlPosition); int GetNextPositionSqlParse(); + bool GetItem(int index, FSQL::view_item& item); int next_item_no_space(int& index, int direction = 1); private: wxString get_list_columns(int startindex, union FSQL::Byte zone); - + void _addfunc(const view_item *vi); + void _addfunc_in_braket(int start,int end); wxPoint align_level(int start_i, int level, int Xpos, int Ypos, int flag); int check_bracket(int index); int get_prev_value(int indx, wxString keyword); @@ -185,8 +195,10 @@ namespace FSQL { wxRect rect; wxString sql; int lastposition = 0; + int errorposition=-1; std::vector items; std::vector listTable; // перечень таблиц синонимов, подзапросов и функций с колонками + std::vector listFunction; // перечень функций используемых в запросе //int recurse(int level); }; } \ No newline at end of file diff --git a/include/utils/FunctionPGHelper.h b/include/utils/FunctionPGHelper.h index b42f73a..5217ea2 100644 --- a/include/utils/FunctionPGHelper.h +++ b/include/utils/FunctionPGHelper.h @@ -5,16 +5,11 @@ #include #include #include -#include -#include -#include - -extern sysSettings* settings; class FunctionPGHelper { public: - FunctionPGHelper() {}; + FunctionPGHelper() {dblast=NULL;}; /// /// Создать только переданный в конструкторе html текст с именем "content" /// @@ -23,6 +18,7 @@ public: body.clear(); Add("content", content); isload = true; + dblast=NULL; }; int Size() { return body.size(); @@ -30,126 +26,23 @@ public: void SetTimerClose(int ms) { m_interval = ms; } int GetTimerClose() { return m_interval; } void Add(const wxString& key, const wxString& v) { body.emplace(key, v); } - wxString getHelpString(wxString fnd, bool isPart = true) { - if (!isValid()) return wxEmptyString; - auto search = body.find(fnd); - wxString txt; - - if (search != body.end()) - txt = search->second; - else - { - std::vector list; - int l = fnd.Len(); - wxString b; - for (const auto& e : body) { - if (e.first.Len() > l && e.first.StartsWith(fnd)) { - list.push_back(e.first); - b = e.second; - } - } - if (list.size() == 1) txt = b; - else { - for (const auto& s : list) { - txt += wxString::Format("%s
", s, s); - } - } - } - //if (i == wxNOT_FOUND) return wxEmptyString; - return txt; - } - wxString getSqlCommandHelp(wxString fnd) { - wxUniChar sep = wxFileName::GetPathSeparator(); - fnd.Replace(" ", ""); - wxString f = wxFindFirstFile(path + sep + "sql-" + fnd + "*.html"); - wxString last, txt; - - int c = 0; - while (!f.empty()) - { - f = f.AfterLast(sep); - last = f; - txt += wxString::Format("%s
", f, f); - f = wxFindNextFile(); - c++; - } - if (last.empty()) { - return wxEmptyString; - } - else if (c == 1) { - return getHelpFile(last); - } - else { - return txt; - } - - } - wxString getHelpFile(wxString filename) { - wxString tempDir = path + wxFileName::GetPathSeparator() + filename; - if (!wxFileExists(tempDir)) return wxEmptyString; - wxTextFile tfile; - tfile.Open(tempDir); - // read the first line - wxString str, sbody; - sbody = tfile.GetFirstLine(); - bool flag = true; - wxRegEx b("()"); - while (!tfile.Eof()) - { - str = tfile.GetNextLine(); - if (flag && b.Matches(str)) { - size_t start, len; - b.GetMatch(&start, &len, 0); - str = str.Mid(start + len); - sbody = ""; - flag = false; - } - sbody += str; - } - return sbody; - - } - bool isValid() { - if (!isload) loadfile(); - return isload; - } + wxString getHelpString(wxString fnd, bool isPart = true) ; + wxString getHelpFile(wxString filename); + wxString getSqlCommandHelp(wxString fnd); + bool isValid(); + void setDbConn(pgConn *db); + // Ищем ключевое слово в объектах БД + wxString getDBinfoKeyword(wxString objname,bool islower); + // Ищем файлы справки для команд sql private: bool isload = false; int m_interval = -1; wxString path; std::map body; - void loadfile() { - if (isload) return; - body.clear(); - path = settings->GetPgHelpPath(); - wxString tempDir = path + "_func.txt"; - //tempDir="C:\\Users\\lsv\\Source\\Repos\\wxHtmlhint\\1"; - if (!wxFileExists(tempDir)) return; - wxTextFile tfile; - tfile.Open(tempDir); - - // read the first line - wxString str, sbody; - wxString name = tfile.GetFirstLine(); - - //wxSortedArrayString names; - name = name.AfterFirst('#'); - // read all lines one by one - // until the end of the file - while (!tfile.Eof()) - { - str = tfile.GetNextLine(); - if (str.Left(1) == '#') { - body.emplace(name, sbody); - sbody = ""; - name = str.AfterFirst('#'); - } - else sbody += str; - } - body.emplace(name, sbody); - isload = true; - }; + // db connect + pgConn *dblast; + void loadfile(); }; #endif diff --git a/include/utils/misc.h b/include/utils/misc.h index 6efcb59..06f379d 100644 --- a/include/utils/misc.h +++ b/include/utils/misc.h @@ -279,5 +279,7 @@ wxString commandLineCleanOption(const wxString &option, bool schemaObject = fals wxString qtIdent(const wxString &value); // add " if necessary wxString qtTypeIdent(const wxString &value); // add " if necessary +bool make_identifier(const wxString &strname, wxString &s, wxString &n, bool islower); + #endif diff --git a/include/utils/popuphelp.h b/include/utils/popuphelp.h index 6880e04..ce0a559 100644 --- a/include/utils/popuphelp.h +++ b/include/utils/popuphelp.h @@ -60,13 +60,17 @@ public: htmlWindow->SetRelatedStatusBar(0); //htmlWindow->SetPage("

TEST

Set Page Works"); - wxString txt = hhelper->getHelpString(keyword); + + wxString txt; + txt=hhelper->getDBinfoKeyword(keyword,true); if (txt.IsEmpty()) { - - txt = hhelper->getSqlCommandHelp(keyword); + txt = hhelper->getHelpString(keyword.Lower()); if (txt.empty()) { - isvalid = false; - return; + txt = hhelper->getSqlCommandHelp(keyword.Lower()); + if (txt.empty()) { + isvalid = false; + return; + } } } SetPage(txt); @@ -131,9 +135,13 @@ public: this->Bind(wxEVT_HTML_LINK_CLICKED, [&](wxHtmlLinkEvent& event) { wxHtmlLinkInfo i = event.GetLinkInfo(); wxString name = i.GetHref(); - wxString body=this->hhelper->getHelpString(name); + wxString body; + body=this->hhelper->getDBinfoKeyword(name,false); if (body.IsEmpty()) { - body = this->hhelper->getHelpFile(name); + body=this->hhelper->getHelpString(name); + if (body.IsEmpty()) { + body = this->hhelper->getHelpFile(name); + } } SetPage(body); //ctext=htmlWindow->SelectionToText(); @@ -153,12 +161,15 @@ public: //wxString body = this->hhelper->getHelpString(name); wxString ctext = htmlWindow->SelectionToText(); if (!ctext.IsEmpty()) { - wxClipboardLocker clip; - if (!clip || - !wxTheClipboard->AddData(new wxTextDataObject(ctext))) + if (wxTheClipboard->Open()) { - + wxDataObjectComposite* dataobj = new wxDataObjectComposite(); + dataobj->Add(new wxTextDataObject(ctext)); + //dataobj->Add(new wxHTMLDataObject(str)); + wxTheClipboard->SetData(dataobj); + wxTheClipboard->Close(); } + wxString wname = GetParent()->GetName(); if (wname == "frmStatus") { //CMD_EVENT_FIND_STR @@ -203,6 +214,7 @@ private: wxSize sizew; FunctionPGHelper* hhelper; std::vector hist; + std::vector hist_viewp; void SetPage(wxString innerbody,bool gethistory=false) { wxString h; int p = innerbody.Find(""); @@ -224,9 +236,22 @@ private: h = hist[hist.size()-1]; } else { + if (hist.size()>0) { + wxPoint ps=htmlWindow->GetViewStart(); + hist_viewp.push_back(ps); + } hist.push_back(h); } htmlWindow->SetPage(h); + if (gethistory && hist_viewp.size()>0) { + wxPoint ps=hist_viewp[hist_viewp.size()-1]; + hist_viewp.pop_back(); + htmlWindow->Scroll(ps); + } else { + // + wxPoint ps(0,0); + htmlWindow->Scroll(ps); + } } private: wxTimer *closeTimer=NULL; diff --git a/mingw-windows-x64.cmake b/mingw-windows-x64.cmake index a085b22..9719d68 100644 --- a/mingw-windows-x64.cmake +++ b/mingw-windows-x64.cmake @@ -17,7 +17,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(WXWIN_LIB /home/sergey/wxWidgets-3.2.2.1/msw-build/lib ) set(WXWIN /home/sergey/k/wxMSW-3.2.2 ) - set(PGDIR /home/sergey/k/pg10 ) + set(PGDIR /home/sergey/k/pg17 ) set(PGBUILD /home/sergey/k ) set(wxWidgets_LIBRARIES "-L${WXWIN_LIB} -lwx_mswu_xrc-3.2-x86_64-w64-mingw32.dll -lwx_mswu_qa-3.2-x86_64-w64-mingw32.dll -lwx_baseu_net-3.2-x86_64-w64-mingw32.dll -lwx_mswu_html-3.2-x86_64-w64-mingw32.dll -lwx_mswu_core-3.2-x86_64-w64-mingw32.dll -lwx_baseu_xml-3.2-x86_64-w64-mingw32.dll -lwx_baseu-3.2-x86_64-w64-mingw32.dll -lwx_mswu_aui-3.2-x86_64-w64-mingw32.dll -lwx_baseu_xml-3.2-x86_64-w64-mingw32.dll -lwx_mswu_stc-3.2-x86_64-w64-mingw32.dll") set(PGLIB "-L${PGDIR}/lib -lpq -lxml2") diff --git a/pgAdmin3.vcxproj b/pgAdmin3.vcxproj index 73c4b47..c7d6933 100644 --- a/pgAdmin3.vcxproj +++ b/pgAdmin3.vcxproj @@ -490,6 +490,7 @@ + diff --git a/pgAdmin3.vcxproj.filters b/pgAdmin3.vcxproj.filters index 089b94d..78dc4ca 100644 --- a/pgAdmin3.vcxproj.filters +++ b/pgAdmin3.vcxproj.filters @@ -808,6 +808,9 @@ utils + + utils + utils diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..9f3dd3d --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,17 @@ + +find_package(Catch2) +add_executable(test_Formatter test_Formatter.cpp ../utils/FormatterSQL.cpp) +#target_include_directories(test_Formatter PRIVATE ${CMAKE_SOURCE_DIR}/include) +#target_link_libraries(test_Formatter Catch2::Catch2) + + +#add_library(test_sources test_Formatter.cpp ) +#target_link_libraries(test_sources Catch2::Catch2) + +#add_executable(tests test.cpp) +#target_link_libraries(tests -Wl,--whole-archive test_sources -Wl,--no-whole-archive) +#target_link_libraries(tests Catch2::Catch2) + +#include(CTest) +include(Catch) +catch_discover_tests(test_Formatter) diff --git a/tests/test_Formatter.cpp b/tests/test_Formatter.cpp new file mode 100644 index 0000000..c86ed8d --- /dev/null +++ b/tests/test_Formatter.cpp @@ -0,0 +1,325 @@ +#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file +#include +#include "utils/FormatterSQL.h" + +using namespace FSQL; + +TEST_CASE( "FormatterSQL build autocomplite", "[fmtsql]" ) { + wxString s = "select a.* from a aa,b"; + FormatterSQL f(s); + wxString act1; + wxString act,cl,t,exp; +SECTION( "Простой запрос" ) { + int e = f.ParseSql(0); + act1=f.BuildAutoComplite(0, 0); + wxString act2 = f.GetListTable(0); + exp = "[ a,aa] \n[ b,] \n"; + CHECK(act2==exp); +} +SECTION( "GetColsList cols" ) { + int e = f.ParseSql(0); + act1=f.BuildAutoComplite(0, 0); + act1 = f.GetColsList("aa.f1",cl, t); + CHECK(act1.ToStdWstring().length()==0 ); +} +SECTION( "GetColsList table" ) { + int e = f.ParseSql(0); + act1=f.BuildAutoComplite(0, 0); + act1 = f.GetColsList("aa.f1", cl, t); + CHECK( t=="a"); +} +SECTION( "GetColsList cols 2" ) { + int e = f.ParseSql(0); + act1=f.BuildAutoComplite(0, 0); + act1 = f.GetColsList("b.*", cl, t); + CHECK(act1.ToStdWstring().length()==0); +} +SECTION( "GetColsList table 2" ) { + int e = f.ParseSql(0); + act1 = f.BuildAutoComplite(0, 0); + act1 = f.GetColsList("b.*", cl, t); + CHECK(t=="b"); +} +SECTION( "Функция 1" ) { + int e; + f.SetSql("select 2 from f() a"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + exp = "[ @,a] \n"; + CHECK(act==exp); +} +SECTION( "func2" ) { + int e; + FSQL::FormatterSQL f3(L"select now() from f() a(f1,\"ф2\")"); + wxString act3; + e = f3.ParseSql(0); + CHECK(e==0); + act = f3.BuildAutoComplite(0, 0); act = f3.GetListTable(0); + exp=R"([ @,a] f1,"ф2" + )"; + CHECK(act3==exp); + act3 = f3.GetListTable(1); + //act3=f3.printParseArray(); + //act3=wxString::Format(" len: %d",act3.Len()); + exp = "[ now,] \n[ f,] \n[ a,] \n"; + //std::cerr << act3; + CHECK(act3==exp); +} +SECTION( "Функция 2a" ) { + int e; + f.SetSql("select 2 from f(p1,(1+2)) a(f1 int,\"ф2\" text)"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + exp = "[ @,a] f1,\"ф2\"\n"; + CHECK(act==exp); +} +SECTION( "Функция 2b" ) { + int e; + f.SetSql("select 2 from f(p1,(1+2)) a(f1 int,\"ф2\" text), t2"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + exp = "[ @,a] f1,\"ф2\"\n[ t2,] \n"; + CHECK(act==exp); +} +SECTION( "Подзапрос 1" ) { + int e; + f.SetSql("select 2 from (select it.* from it) t1, t2"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + exp = "[ it,] \n[ @,t1] it.*\n[ t2,] \n"; + CHECK(act==exp); +} +SECTION( "Подзапрос 2" ) { + int e; + f.SetSql("select 2 from (select it.* from it) t1(f1,f2), t2 al(f3,f4)"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + exp = "[ it,] \n[ @,t1] f1,f2\n[ t2,al] f3,f4\n"; + CHECK(act==exp); + act = f.GetListTable(1); + exp = "[ t1,] \n[ al,] \n"; + CHECK(act==exp); + +} +SECTION( "Подзапрос 3" ) { + int e; + f.SetSql("select 2 from (select it.dt::time at zone Ndt,it.col2 from it) t1, t2 al(f3,f4)"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + exp = "[ it,] \n[ @,t1] Ndt,it.col2\n[ t2,al] f3,f4\n"; + CHECK(act==exp); +} +SECTION( "GetColsList cols 3" ) { + int e; + f.SetSql("select 2 from (select it.dt::time at zone Ndt,it.col2 from it) t1, t2 al(f3,f4)"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + exp = "[ it,] \n[ @,t1] Ndt,it.col2\n[ t2,al] f3,f4\n"; + act = f.GetColsList("t1.n", cl, t); + exp = "Ndt"; + CHECK(act==exp); +} +SECTION( "GetColsList table" ) { + int e; + f.SetSql("select 2 from (select it.dt::time at zone Ndt,it.col2 from it) t1, t2 al(f3,f4)"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + act1 = f.GetColsList("t1.n", cl, t); + exp = "t1"; + CHECK(t==exp); +} +SECTION( "GetColsList cols 4" ) { + int e; + f.SetSql("select 2 from (select it.dt::time at zone Ndt,it.col2 from it) t1, t2 al(f3,f4)"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + exp = "[ it,] \n[ @,t1] Ndt,it.col2\n[ t2,al] f3,f4\n"; + CHECK(act==exp); + act1 = f.GetColsList("t1.*", cl, t); + exp = "Ndt\tcol2"; + CHECK(act1==exp); +} +SECTION( "Join 1" ) { + int e; + // join + f.SetSql("select * from def join param on def.id=param.def join inv_type c on c.id=param.type limit 5"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + exp = "[ def,] \n[ param,] \n[ inv_type,c] \n"; + CHECK(act==exp); +} +SECTION( "Join 1.1" ) { + int e; + f.SetSql("select * from def d join param p on d.id=p.def join inv_type c on c.id=p.type where"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + exp = L"[ def,d] \n[ param,p] \n[ inv_type,c] \n"; + CHECK(act==exp); +} +SECTION( "Join 2" ) { + int e; + f.SetSql("select def.*,param.* from def join ( select f2,f3 from a) param on def.id=param.def join inv_type c on c.id=param.type"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + exp = "[ def,] \n[ a,] \n[ @,param] f2,f3\n[ inv_type,c] \n"; + CHECK(act==exp); +} +SECTION( "with 1" ) { + int e; + // with + f.SetSql("with a as (select in1,i2 from def), b(b1,b2) as (select t3 from z2) select * from b"); + e = f.ParseSql(0); + act = f.BuildAutoComplite(0, 0); act = f.GetListTable(0); + exp = "[ def,] \n[ @,a] in1,i2\n[ z2,] \n[ @,b] b1,b2\n[ b,] \n"; + CHECK(act==exp); +} +} + +TEST_CASE( "FormatterSQL parse", "[parsesql]" ) { + int e; + FormatterSQL f2("()"); + wxString exp,o,s; + SECTION( "simple 1" ) { + wxString s = "select * from table t1,table t2 where t1.f=t2.f order by 1 desc limit 2"; + FormatterSQL f(s); + int e=f.ParseSql(0); + CHECK(e==0); + } + SECTION( "empty braket" ) { + e = f2.ParseSql(0); + CHECK(e==0); + o = f2.printParseArray(); + exp = "Index: 0 Jump 1 Type: 5 widt: 1 Flag: 0 Val : (\nIndex: 1 Jump 0 Type: 5 widt: 1 Flag: 0 Val : )\n"; + CHECK(o==exp); + } + SECTION( "variant1" ) { + f2.SetSql("a.* 0:1 b::t (m).c 2~!@6"); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 7 widt: 1 Flag: 0 Val : a\nIndex: 1 Type: 4 widt: 2 Flag: 0 Val : .*\nIndex: 2 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 3 Type: 8 widt: 1 Flag: 0 Val : 0\nIndex: 4 Type: 4 widt: 1 Flag: 0 Val : :\nIndex: 5 Type: 8 widt: 1 Flag: 0 Val : 1\nIndex: 6 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 7 Type: 7 widt: 1 Flag: 0 Val : b\nIndex: 8 Type: 4 widt: 2 Flag: 0 Val : ::\nIndex: 9 Type: 7 widt: 1 Flag: 256 Val : t\nIndex: 10 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 11 Jump 13 Type: 5 widt: 1 Flag: 0 Val : (\nIndex: 12 Type: 7 widt: 1 Flag: 0 Val : m\nIndex: 13 Jump 11 Type: 5 widt: 1 Flag: 0 Val : )\nIndex: 14 Type: 4 widt: 1 Flag: 0 Val : .\nIndex: 15 Type: 7 widt: 1 Flag: 0 Val : c\nIndex: 16 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 17 Type: 8 widt: 1 Flag: 0 Val : 2\nIndex: 18 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 19 Type: 4 widt: 3 Flag: 0 Val : ~!@\nIndex: 20 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 21 Type: 8 widt: 1 Flag: 0 Val : 6\n"; + CHECK(o==exp); + } + + SECTION( "many variantns" ) { + f2.SetSql( "--1" ); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 10 widt: 3 Flag: 0 Val : --1\n"; + CHECK(o==exp); + + f2.SetSql("$$ fsdf "); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 3 widt: 8 Flag: 0 Val : $$ fsdf \n"; + CHECK(o==exp); + + f2.SetSql("E' "); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = ""; + CHECK(o==exp); + + f2.SetSql("/*"); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 10 widt: 2 Flag: 0 Val : /*\n"; + CHECK(o==exp); + + s = "a.b"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 6 widt: 3 Flag: 0 Val : a.b\n"; + CHECK(o==exp); + s = "a::b"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 7 widt: 1 Flag: 0 Val : a\nIndex: 1 Type: 4 widt: 2 Flag: 0 Val : ::\nIndex: 2 Type: 7 widt: 1 Flag: 0 Val : b\n"; + CHECK(o==exp); + s = "is not null"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 2 widt: 11 Flag: 0 Val : is not null\n"; + CHECK(o==exp); + s = "*,+:%@!~"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 4 widt: 1 Flag: 0 Val : *\nIndex: 1 Type: 4 widt: 1 Flag: 0 Val : ,\nIndex: 2 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 3 Type: 4 widt: 6 Flag: 0 Val : +:%@!~\n"; + CHECK(o==exp); + + s = "1||2"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 8 widt: 1 Flag: 0 Val : 1\nIndex: 1 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 2 Type: 4 widt: 2 Flag: 0 Val : ||\nIndex: 3 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 4 Type: 8 widt: 1 Flag: 0 Val : 2\n"; + CHECK(o==exp); + + s = "1+2"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 8 widt: 1 Flag: 0 Val : 1\nIndex: 1 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 2 Type: 4 widt: 1 Flag: 0 Val : +\nIndex: 3 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 4 Type: 8 widt: 1 Flag: 0 Val : 2\n"; + CHECK(o==exp); + s = "e'\\n'"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 3 widt: 5 Flag: 0 Val : e'\\n'\n"; + CHECK(o==exp); + + s = "u&'d\\0061t\\+000061'"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 3 widt: 19 Flag: 0 Val : u&'d\\0061t\\+000061'\n"; + CHECK(o==exp); + s = "$$ $$"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 3 widt: 5 Flag: 0 Val : $$ $$\n"; + CHECK(o==exp); + s = "$a$ $ $a$"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 3 widt: 9 Flag: 0 Val : $a$ $ $a$\n"; + CHECK(o==exp); + s = "$0"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "\n"; + //Assert::AreEqual(exp.ToStdWstring(), o.ToStdWstring(), s); + s = "f(a,b)"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 7 widt: 1 Flag: 256 Val : f\nIndex: 1 Jump 6 Type: 5 widt: 1 Flag: 0 Val : (\nIndex: 2 Type: 7 widt: 1 Flag: 0 Val : a\nIndex: 3 Type: 4 widt: 1 Flag: 0 Val : ,\nIndex: 4 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 5 Type: 7 widt: 1 Flag: 0 Val : b\nIndex: 6 Jump 1 Type: 5 widt: 1 Flag: 0 Val : )\n"; + //Assert::AreEqual(exp.ToStdWstring(), o.ToStdWstring(), s); + s = ")"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "\n"; + CHECK(e==-1); + s = "("; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + CHECK(e==-1); + exp = "\n"; + //Assert::AreEqual(exp.ToStdWstring(), o.ToStdWstring(), s); + s = "like"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 2 widt: 4 Flag: 0 Val : like\n"; + CHECK(o==exp); + + s = "("; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "\n"; + //Assert::AreEqual(exp.ToStdWstring(), o.ToStdWstring(), s); + + s = "x=ANY([1,2,3])"; f2.SetSql(s); e = f2.ParseSql(0); o = f2.printParseArray(); + exp = "Index: 0 Type: 7 widt: 1 Flag: 0 Val : x\nIndex: 1 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 2 Type: 4 widt: 1 Flag: 0 Val : =\nIndex: 3 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 4 Type: 7 widt: 3 Flag: 256 Val : ANY\nIndex: 5 Jump 15 Type: 5 widt: 1 Flag: 0 Val : (\nIndex: 6 Jump 14 Type: 5 widt: 1 Flag: 0 Val : [\nIndex: 7 Type: 8 widt: 1 Flag: 0 Val : 1\nIndex: 8 Type: 4 widt: 1 Flag: 0 Val : ,\nIndex: 9 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 10 Type: 8 widt: 1 Flag: 0 Val : 2\nIndex: 11 Type: 4 widt: 1 Flag: 0 Val : ,\nIndex: 12 Type: 1 widt: 1 Flag: 0 Val : \nIndex: 13 Type: 8 widt: 1 Flag: 0 Val : 3\nIndex: 14 Jump 6 Type: 5 widt: 1 Flag: 0 Val : ]\nIndex: 15 Jump 5 Type: 5 widt: 1 Flag: 0 Val : )\n"; + CHECK(o==exp); + } +} +TEST_CASE( "FormatterSQL parsePlpgsql", "[parsesql]" ) { + int e; + wxString exp,o,s; + SECTION( "variant1" ) { + FormatterSQL f2(R"( + declare + begin + end + )"); + std::vector list = f2.ParsePLpgsql(); o=f2.GetListTable(list); + exp = ""; + CHECK(o==exp); + } +SECTION( "plpgsql 1" ) { + FormatterSQL f2(R"( + declare + x record; + iobj varchar(50) :=add_part(now(),'1 month'); + begin + for i in 11..22 + loop + end loop; + end + )"); + std::vector list = f2.ParsePLpgsql(); o=f2.GetListTable(list); + exp = "[ add_part,] \n[ now,] \n"; + CHECK(o==exp); +} +SECTION( "plpgsql 2" ) { + FormatterSQL f2(R"( + declare + x record; + begin + for i in 11..22 + loop + case f1() when f2() else +select a into ii from tab1 t where f3() and f4() in ('a'); +case end; +delete from t2 using t4,t5 where t4.id=t5 and t2.id=t4.id returning t2.id; + end loop; + end + )"); + std::vector list = f2.ParsePLpgsql(); o=f2.GetListTable(list); + exp = "[ f1,] \n[ f2,] \n[ tab1,] \n[ f3,] \n[ f4,] \n[ t2,] \n[ t5,] \n"; + CHECK(o==exp); +} + +} + diff --git a/utils/FormatterSQL.cpp b/utils/FormatterSQL.cpp index dbcd00c..0d08647 100644 --- a/utils/FormatterSQL.cpp +++ b/utils/FormatterSQL.cpp @@ -67,14 +67,20 @@ wxString FormatterSQL::get_list_columns(int startindex, union Byte zone) { } return cols; } -wxString FormatterSQL::GetListTable(int cursorPos) { +wxString FormatterSQL::GetListTable(const std::vector &list) { int s = 0; wxString r = ""; - while (s < listTable.size()) { - complite_element* el = &listTable[s++]; - r += wxString::Format("[ %s,%s] %s\n", el->table, el->alias, el->columnList); + complite_element el; + while (s < list.size()) { + el = list[s++]; + r += wxString::Format("[ %s,%s] %s\n", el.table, el.alias, el.columnList); } return r; + +} +wxString FormatterSQL::GetListTable(int cursorPos) { + if (cursorPos==0) return GetListTable(listTable); + else return GetListTable(listFunction); } int FormatterSQL::GetTableListBeforePosition(int positem, wxArrayString& listtable, wxArrayString& listalias) { int s = 0; @@ -198,6 +204,15 @@ iteration2: } return r; } +void FormatterSQL::_addfunc(const view_item *vi) { + if (vi->flags & f_key::isFUNCTION ) { + complite_element ff; + ff.table=vi->txt; + ff.startIndex=vi->srcpos; + listFunction.push_back(ff); + } + +} wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) { int len_items = items.size(); int n_element = startIndex; @@ -214,8 +229,12 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) { wxString cols_name; bool isfunction = false; bool isskipnext = false; + bool isinsert = false; el.columnList = ""; el.alias = ""; el.table = ""; el.level = level; - if (level == 0) listTable.clear(); + if (level == 0) { + listTable.clear(); + listFunction.clear(); + } while (next_item_no_space(found_index) != -1) { view_item* vi = &items[found_index]; if (vi->type == comment) { @@ -224,7 +243,7 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) { } if (vi->type == keyword) { union Byte z = zone; - if (vi->txt.Lower() == "from" && vi->flags != 0) { + if ((vi->txt.Lower() == "from" && vi->flags != 0) ) { if (zone.b.select_list) { if (!lastname.IsEmpty())cols.Add(lastname); } @@ -241,12 +260,29 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) { cols.Clear(); //el.startIndex = found_index + 1; } - if (vi->txt.Lower().Find("join") > -1) { + if ((vi->txt.Lower().Find("join") > -1) || vi->txt.Lower() == "using") { goto close_element_from; } if (vi->txt.Lower() == "on") { goto close_element_from; } + if (vi->txt.Lower() == "insert") { + found_index++; + if ((next_item_no_space(found_index)!=-1) && items[found_index].txt.Lower()=="into") { + found_index++; + if ((next_item_no_space(found_index)!=-1) && (items[found_index].type==FSQL::type_item::identifier ||items[found_index].type==FSQL::type_item::name)) { + // table name + + complite_element el2; + el2.table=items[found_index].txt; + el2.startIndex=el.endIndex=found_index; + el2.level=level; + found_index++; + listTable.push_back(el2); + } + } + continue; + } if ((vi->flags & end_from) != 0) { zone.b.from = 0; @@ -320,9 +356,19 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) { } else if (isskipnext) { - // пропускаем всё до следующей запятой + // пропускаем всё до следующей запятой но проверим наличие функций + if (vi->txt=='(') { + int jump = vi->endlevel; + _addfunc_in_braket(found_index,jump); + found_index = jump + 1; + continue; + } + + if (vi->type == identifier || vi->type == name) _addfunc(vi); } else if (vi->type == identifier || vi->type == name) { + if (zone.b.with==0) + _addfunc(vi); int i = found_index - 1; if (next_item_no_space(i) != -1) { if (items[i].type == separation && items[i].txt == "::") { @@ -352,6 +398,7 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) { else if (vi->txt == '(') { int jump = vi->endlevel; if (zone.b.select_list) { + _addfunc_in_braket(found_index,jump); found_index = jump + 1; continue; } @@ -392,6 +439,7 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) { } // это функция с аргументами if (!isfunction) { + _addfunc_in_braket(found_index,jump); isfunction = true; found_index = jump + 1; continue; @@ -407,13 +455,15 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) { if (isfunction) el.table = "@"; else - if (objName.Count() > 0) el.table = objName[0]; + if (objName.Count() > 0) {el.table = objName[0]; el.startIndex=indexlastID; } else el.table = "-"; el.alias = lastname; el.columnList = cols_name; listTable.push_back(el); } + // check run functions + _addfunc_in_braket(found_index,jump); isskipnext = true; found_index = jump + 1; continue; @@ -461,9 +511,118 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) { else return wxJoin(cols, ','); } +void FormatterSQL::_addfunc_in_braket(int start,int end) { + while (++start <= end) { + if (items[start].txt=='(') { + int tmp=start-1; + if (next_item_no_space(tmp,-1) != -1) { + view_item *v2=&items[tmp]; + if (v2->type == identifier || v2->type == name ) _addfunc(v2); + } + } + } + +} int FormatterSQL::GetNextPositionSqlParse() { return lastposition; } +/// Анализируем исходный текст процедуры или функции +/// Находим все функции таблицы и представления +/// +std::vector FormatterSQL::ParsePLpgsql(){ + int e; + int maxlen=sql.Len(); + std::vector listdbobject; + complite_element i; + // Глобальный цикл по тексту + while (lastposition0) { + //std::cout << currentsql << std::endl ; + } + // извлечем информацию о таблицах и функциях + BuildAutoComplite(0,0); + // + for(int i=0;i0) { + bool add=true; + for(int k=0;k0) { + complite_element it; + wxString fn=listFunction[j].table; + if ( + fn.Lower()=="varchar"|| + fn.Lower()=="char"|| + fn.Lower()=="chr"|| + fn.Lower()=="btree"|| + fn.Lower()=="numeric"|| + fn.Lower()=="bit"|| + fn.Lower()=="return"|| + fn.Lower()=="varying"|| + fn.Lower()=="key"|| + fn.Lower()=="range"|| + fn.Lower()=="varbit" + ) continue; + it.table=fn; + it.startIndex=listFunction[j].startIndex; + wxString check=sql.substr(it.startIndex,fn.Len()); + if (check!=fn) { + std::cout << fn << std::endl; + } + listdbobject.push_back(it); + } + } + + } + // sort startIndex + std::sort(listdbobject.begin(),listdbobject.end(), + [](const complite_element& a, const complite_element& b) { + return a.startIndex < b.startIndex; + }); + +return listdbobject; +} /// /// ParseSql Выполнение разбора текста как SQL выражения /// @@ -471,6 +630,7 @@ int FormatterSQL::GetNextPositionSqlParse() { /// Возвращает код ошибки если SQL выражение было не полное или не корректное int FormatterSQL::ParseSql(int flags) { int i = lastposition; + errorposition=-1; int lhome = 0; bool str_literal = false; bool ext = false; @@ -662,6 +822,11 @@ int FormatterSQL::ParseSql(int flags) { int pos = tmp.Find(dollarSep); if (pos != -1) { vi.txt = sql.substr(i - l, l + l + pos); + if (flags == 1 && dollarSep=="$BODY$") { + vi.txt=dollarSep; + pos=0;l=0; + } + i += pos + l; } else { @@ -677,6 +842,7 @@ int FormatterSQL::ParseSql(int flags) { if (c == '\'' || c == '"') { qt = c; str_literal = true; + vi.srcpos=i-1; cons = qt; continue; } @@ -815,11 +981,20 @@ int FormatterSQL::ParseSql(int flags) { bracket.pop(); } else { + errorposition=i+tmp.Len()-1; return -1; //braket no parent } vi.endlevel = idx; if (idx != -1) items[idx].endlevel = items.size(); + } else if (keyEntities[n].name == "then"|| + keyEntities[n].name == "loop" ) { // for plpgsql + // if ... then + // while ... loop or for .. in query loop + if (bracket.size() == 0) { + i=i+tmp.Len()-1; + break; + } } } i += tmp.Len() - 1; @@ -837,7 +1012,7 @@ int FormatterSQL::ParseSql(int flags) { int pp2 = items.size() - 1; int pp3 = next_item_no_space(pp2, -1); if (pp3 != -1 && (items[pp3].type == name || items[pp3].type == identifier)) { - vi.flags = isFUNCTION; + items[pp3].flags = isFUNCTION; } } continue; @@ -850,6 +1025,7 @@ int FormatterSQL::ParseSql(int flags) { bracket.pop(); } else { + errorposition=i; return -1; //braket no parent } vi.txt = c; @@ -866,6 +1042,7 @@ int FormatterSQL::ParseSql(int flags) { if (matches) { tmp = regident.GetMatch(tmp, 0); vi.txt = tmp; + vi.srcpos=i; vi.type = name; i += tmp.Len(); continue; @@ -930,15 +1107,19 @@ int FormatterSQL::ParseSql(int flags) { } // no sql command if (ex) break; + errorposition=i; return -3; } // end big loop if (str_literal) { + errorposition=i; return -2; // literal no close } - if (bracket.size() > 0) + if (bracket.size() > 0) { + errorposition=i; return -1; // bracet no close + } lastposition = i; return 0; } diff --git a/utils/FunctionPGHelper.cpp b/utils/FunctionPGHelper.cpp new file mode 100644 index 0000000..cefde58 --- /dev/null +++ b/utils/FunctionPGHelper.cpp @@ -0,0 +1,341 @@ +#include "pgAdmin3.h" +#include +#include +#include +#include +#include "db/pgSet.h" +#include "frm/frmMain.h" +#include "ctl/ctlSQLBox.h" + +extern sysSettings* settings; +extern frmMain *winMain; + + +wxString FunctionPGHelper::getHelpString(wxString fnd, bool isPart) +{ + if (!isValid()) return wxEmptyString; + auto search = body.find(fnd); + wxString txt; + + if (search != body.end()) + txt = search->second; + else + { + std::vector list; + int l = fnd.Len(); + wxString b; + for (const auto& e : body) { + if (e.first.Len() > l && e.first.StartsWith(fnd)) { + list.push_back(e.first); + b = e.second; + } + } + if (list.size() == 1) txt = b; + else { + for (const auto& s : list) { + txt += wxString::Format("%s
", s, s); + } + } + } + //if (i == wxNOT_FOUND) return wxEmptyString; + return txt; +} +void FunctionPGHelper::setDbConn(pgConn *db) { + dblast=db; +} +#include "schema/pgDatabase.h" +#include "schema/pgSchema.h" +#include "schema/pgTable.h" +#include "schema/pgView.h" +#include "schema/pgFunction.h" +#include "schema/pgTrigger.h" + +extern pgDatabaseFactory databaseFactory; +extern pgSchemaFactory schemaFactory; +extern pgTableFactory tableFactory; +extern pgViewFactory viewFactory; +extern pgFunctionFactory functionFactory; +extern pgProcedureFactory procedureFactory; +extern pgTriggerFunctionFactory triggerFunctionFactory; +wxString FunctionPGHelper::getDBinfoKeyword(wxString objname, bool islower) { + if (dblast) { + wxString s,n,a,tmp; + tmp=objname; + a=objname.AfterFirst('('); + if (!a.IsEmpty()) { + tmp=objname.BeforeFirst('('); + a=a.substr(0,a.Len()-1); + } else a="%"; + make_identifier(tmp,s,n,islower); + if (s.IsEmpty()) { + s="%"; + } +wxString querytemplate=R"( +select p.oid,p.relnamespace::regnamespace nsp,p.relname objname, p.relkind::text, +case when p.relkind in ('v','m') +then +--pg_get_viewdef(p.oid,true) +'' +else +'' +end + define , '' args, obj_description(oid,'pg_class') comment from pg_class p +where p.relkind in ('r','p','m','v') and p.relnamespace::regnamespace::text not in ('pg_catalog','information_schema') +and p.relnamespace::regnamespace::text like '%s' and p.relname like '%s' +and (select count(*) from pg_partition_ancestors(oid) ) <=1 +union all +select p.oid,p.pronamespace::regnamespace nsp,p.proname objname, +case when pg_get_function_result(p.oid) is null then 'P' + when pg_get_function_result(p.oid) = 'trigger' then 't' else 'f' end, +'' prosrc ,pg_get_function_arguments(oid), obj_description(oid,'pg_proc') from pg_proc p +where p.pronamespace::regnamespace::text like '%s' and p.proname like '%s' and pg_get_function_arguments(oid) like '%s' +and p.pronamespace::regnamespace::text not in ('pg_catalog','information_schema') +order by objname; +)"; + wxString es=s; + wxString en=n; + es.Replace("'","''"); + en.Replace("'","''"); + wxString sql=wxString::Format(querytemplate + ,es,en,es,en,a); + pgSet *res = dblast->ExecuteSet(sql); + wxString txt,ns,on,kind,args,def,oid; + bool isfunc=false; + int c=0; + while (!res->Eof()) + { + ns = res->GetVal("nsp"); + oid = res->GetVal("oid"); + on = res->GetVal("objname"); + kind = res->GetVal("relkind"); + args = res->GetVal("args"); + def = res->GetVal("define"); + wxString link=ns+"."+on; + if (kind=="f" || kind=="p") { + link+="("+args+")"; + isfunc=true; + } else isfunc=false; + txt += wxString::Format("%s
", link, link); + c++; + res->MoveNext(); + } + if(res) + { + delete res; + res = NULL; + } + if (c>1) return txt; + if (c==1) + { + wxString html; + if (def.IsEmpty()) + { + wxTreeItemIdValue foldercookie; + ctlTree *browser=winMain->GetBrowser(); + wxTreeItemId folderitem = browser->GetFirstChild(browser->GetRootItem(), foldercookie); + + while (folderitem) + { + if (browser->ItemHasChildren(folderitem)) + { + wxTreeItemIdValue servercookie; + wxTreeItemId serveritem = browser->GetFirstChild(folderitem, servercookie); + wxString host=dblast->GetHost(); + wxString db=dblast->GetDbname(); + int port=dblast->GetPort(); + wxString fullid=on; + if (!args.IsEmpty()) fullid+="("+args+")"; + wxString idf=wxString::Format("%s:%d",host,port); + while (serveritem) + { + pgServer *server = (pgServer *)browser->GetItemData(serveritem); + if (server != NULL && server->IsCreatedBy(serverFactory)) { + if (server->GetIdentifier()==idf ) { + if (server->connection()) { + // is open connect + //wxTreeItemId serveritemc1 = browser->GetFirstChild(folderitem, servercookie); + pgCollection *coll=browser->FindCollection(databaseFactory,serveritem); + wxTreeItemId dbssId; + if (coll) dbssId=coll->GetId(); + wxCookieType cookie2; + wxTreeItemId item = browser->GetFirstChild(dbssId, cookie2); + + while (item && item.IsOk()) + { + //wxString tt=browser->GetItemText(item); + pgObject *obj = browser->GetObject(item); + if (obj && obj->IsCreatedBy(databaseFactory) && db==obj->GetName() ) + { + pgCollection *coll=browser->FindCollection(schemaFactory,item); + wxTreeItemId schemasId; + if (coll) schemasId=coll->GetId(); + if (schemasId) { + pgObject *obj2 = browser->GetObject(item); + //obj2->expandedKids + wxCookieType cookie4; + item = browser->GetFirstChild(schemasId, cookie4); + while (item) + { + pgSchema *obj3 = (pgSchema *)browser->GetObject(item); + if (obj3 && obj3->IsCreatedBy(schemaFactory) && ns==obj3->GetName() ) { + obj3->ShowTreeDetail(browser); + wxTreeItemId objcollId; + pgCollection *coll=NULL; + if (kind=='r'||kind=='p' ) coll = browser->FindCollection(tableFactory,item); + if (kind=='v'||kind=='m') coll = browser->FindCollection(viewFactory,item); + if (kind=='f') coll = browser->FindCollection(functionFactory,item); + if (kind=='P') coll = browser->FindCollection(procedureFactory,item); + //if (kind=='p') coll = browser->FindCollection(pg_partitionFactory,item); + if (kind=='t') coll = browser->FindCollection(triggerFunctionFactory,item); + if (coll) { + objcollId=coll->GetId(); + pgObject *obj4 = browser->GetObject(objcollId); + obj4->ShowTreeDetail(browser); + } + wxCookieType cookie5; + item = browser->GetFirstChild(objcollId, cookie5); + while (item) + { + pgObject *obj5 = browser->GetObject(item); + //wxString soid=obj5->GetOidStr(); + if (obj5 && NumToStr(obj5->GetOid())==oid) { + pgObject *obj5 = browser->GetObject(item); + obj5->ShowTreeDetail(browser); + def=obj5->GetSql(browser); + if (kind=='r'|| kind=='m' || kind=='v' || kind=='f') { + //pgTable *o=(pgTable *)(obj5); + //brower->FindObject(tableFactory,) + //o->AppendStuff(def,browser,tableFactory); + } + goto exitloop; + } + item = browser->GetNextChild(objcollId, cookie5); + + } + + } + item = browser->GetNextChild(schemasId, cookie4); + } + } + goto exitloop; + } + item = browser->GetNextChild(dbssId, cookie2); + } // next + + } + + goto exitloop; + } + } + serveritem = browser->GetNextChild(folderitem, servercookie); + } + } + folderitem = browser->GetNextChild(browser->GetRootItem(), foldercookie); + } + } + exitloop: + FSQL::FormatterSQL f(def); + std::vector listobj=f.ParsePLpgsql(); + ctlSQLBox* box = new ctlSQLBox((wxWindow*) winMain, CTL_SQLQUERY, wxDefaultPosition, wxSize(0, 0), wxTE_MULTILINE | wxTE_RICH2); + box->SetText(def); + int l = def.Length(); + box->Colourise(0, box->GetLength()); + //bg = box->SetSQLBoxColourBackground(false).GetAsString(wxC2S_CSS_SYNTAX); + html = box->TextToHtml(0, box->GetLength(),false,listobj); + wxColour cbg=box->GetBackgroundColour(); + wxString bg=cbg.GetAsString(wxC2S_HTML_SYNTAX); + delete box; + html = "" + html + ""; + return html; + } + + } + + return wxEmptyString; +} +wxString FunctionPGHelper::getSqlCommandHelp(wxString fnd) { + wxUniChar sep = wxFileName::GetPathSeparator(); + fnd.Replace(" ", ""); + wxString f = wxFindFirstFile(path + sep + "sql-" + fnd + "*.html"); + wxString last, txt; + + int c = 0; + while (!f.empty()) + { + f = f.AfterLast(sep); + last = f; + txt += wxString::Format("%s
", f, f); + f = wxFindNextFile(); + c++; + } + if (last.empty()) { + return wxEmptyString; + } + else if (c == 1) { + return getHelpFile(last); + } + else { + return txt; + } + +} +wxString FunctionPGHelper::getHelpFile(wxString filename) { + wxString tempDir = path + wxFileName::GetPathSeparator() + filename; + if (!wxFileExists(tempDir)) return wxEmptyString; + wxTextFile tfile; + tfile.Open(tempDir); + // read the first line + wxString str, sbody; + sbody = tfile.GetFirstLine(); + bool flag = true; + wxRegEx b("()"); + while (!tfile.Eof()) + { + str = tfile.GetNextLine(); + if (flag && b.Matches(str)) { + size_t start, len; + b.GetMatch(&start, &len, 0); + str = str.Mid(start + len); + sbody = ""; + flag = false; + } + sbody += str; + } + return sbody; + +} +bool FunctionPGHelper::isValid() { + if (!isload) loadfile(); + return isload; +} +void FunctionPGHelper::loadfile() { + if (isload) return; + body.clear(); + path = settings->GetPgHelpPath(); + wxString tempDir = path + "_func.txt"; + //tempDir="C:\\Users\\lsv\\Source\\Repos\\wxHtmlhint\\1"; + if (!wxFileExists(tempDir)) return; + wxTextFile tfile; + tfile.Open(tempDir); + + // read the first line + wxString str, sbody; + wxString name = tfile.GetFirstLine(); + + //wxSortedArrayString names; + name = name.AfterFirst('#'); + // read all lines one by one + // until the end of the file + while (!tfile.Eof()) + { + str = tfile.GetNextLine(); + if (str.Left(1) == '#') { + body.emplace(name, sbody); + sbody = ""; + name = str.AfterFirst('#'); + } + else sbody += str; + } + body.emplace(name, sbody); + isload = true; +}; diff --git a/utils/misc.cpp b/utils/misc.cpp index 380ee03..31b4004 100644 --- a/utils/misc.cpp +++ b/utils/misc.cpp @@ -1465,4 +1465,21 @@ bool getArrayFromCommaSeparatedList(const wxString &str, wxArrayString &res) return true; } - +bool make_identifier(const wxString &strname, wxString &s, wxString &n, bool islower) { + s=strname.BeforeFirst('.'); + if (s==strname) { + n=strname; + s=""; + } else { + // full name + n=strname.AfterFirst('.'); + } + // + if (n.Find('"')>=0) { + n.Replace("\"",""); + } else {if (islower) n=n.MakeLower();} + if (s.Find('"')>=0) { + s.Replace("\"",""); + } else {if (islower) s=s.MakeLower();} + return true; +}