pgadmin3/utils/FormatterSQL.cpp
2026-03-18 09:09:01 +05:00

1598 lines
56 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "pgAdmin3.h"
#include "wx/regex.h"
#include <stack>
using namespace FSQL;
wxString FormatterSQL::printParseArray() {
int i = 0;
wxString s;
for (auto& it : items) {
wxString e;
//s.Append(wxString::Format("Index: %d\n",i));
if (it.endlevel != -1) s.Append(wxString::Format("Index: %d Jump %d", i, it.endlevel));
else s.Append(wxString::Format("Index: %d", i));
s.Append(wxString::Format(" Type: %d", it.type));
s.Append(wxString::Format(" widt: %d", it.width));
s.Append(wxString::Format(" Flag: %d", it.flags));
s.Append(wxString::Format(" Val : %s\n", it.txt));
i++;
}
return s;
};
int FormatterSQL::GetIndexItemNextSqlPosition(int sqlPosition) {
int p = -1;
view_item vi;
for (auto& it : items) {
p++;
if (it.srcpos < sqlPosition) continue;
while (p > 0 && items[--p].srcpos == -1) {};
vi = items[p];
return p;
}
return items.size() - 1;
};
bool FormatterSQL::GetItem(int index, FSQL::view_item& item) {
if (index >= 0 && index < items.size()) {
item = items[index];
return true;
}
return false; // Error
};
view_item FormatterSQL::GetLastItem() {
view_item item;
if (items.size()>0) {
item = items[items.size()-1];
}
return item;
};
wxString FormatterSQL::get_list_columns(int startindex, union Byte zone) {
wxString cols;
wxArrayString ar;
int i = startindex;
int ngroup = 0;
int nbracket = 0;
if (zone.b.from || zone.b.with) {
while (next_item_no_space(i) != -1) {
if (items[i].txt == ',')
{
ngroup = 0;
}
if (items[i].txt == '(') nbracket++;
if (items[i].type == name || items[i].type == identifier)
{
if (ngroup < 1) ar.Add(items[i].txt); // only first name
ngroup++;
}
if (items[i].txt == ')')
if (--nbracket == 0) break;
i++;
}
cols = wxJoin(ar, ',');
}
return cols;
}
wxString FormatterSQL::GetListTable(const std::vector<complite_element> &list) {
int s = 0;
wxString r = "";
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;
complite_element* el;
int left_id = -1;
while (s < listTable.size()) {
el = &listTable[s++];
//r += wxString::Format("[ %s,%s] %s\n", el->table, el->alias, el->columnList);
bool bet = el->startIndex <= positem && el->endIndex >= positem;
if (el->startIndex > positem) { s--; break; }
if (bet ) {
if (left_id != -1 && (el->endIndex - el->startIndex) >= (&listTable[left_id].endIndex - &listTable[left_id].startIndex)) continue;
left_id=s-1;
}
}
int c = 0;
if (s > 0) {
s = s - 1;
if (left_id != -1) s = left_id;
int level = -1;
while (s >= 0) {
el = &listTable[s--];
if (level == -1) level = el->level;
if (el->table != '@') {
if (level > el->level) break;
if (level < el->level) continue;
listtable.Add(el->table);
listalias.Add(el->alias);
c++;
}
}
}
return c;
}
wxString FormatterSQL::GetColsList(wxString what, wxString& listfieldOut, wxString& nameTableOut) {
wxString r = "";
wxString f = "";
wxString t = "";
nameTableOut = "";
wxArrayString ar = wxSplit(what, '.');
int iTab = 0;
int iFld = 1;
if (ar.GetCount() > 2) { iTab++; iFld++; }
if (ar.GetCount() == 1) { iTab = 0; iFld = -1; }
bool astreplace = false;
if (iFld != -1 && ar[iFld] == '*') astreplace = true;
if (iFld != -1 && !astreplace) f = ar[iFld].Lower();
t = ar[iTab].Lower();
std::map<wxString, int> tablename;
// check recursive
// removed dublicate empty
//
//
wxString tmp = t;
int nc = 20; // max depth recursion
iteration_remove_dublicate:
int k = 0;
tablename.clear();
nc--;
while (nc > 0 && k < listTable.size()) {
complite_element* el = &listTable[k++];
if (el->table.Lower() == tmp && el->columnList.Len() == 0) { // for table
tablename[tmp] = k - 1;
}
else if (el->table != "@" && el->alias.Lower() == tmp && el->columnList.Len() == 0) { // for alias
tmp = el->table.Lower();
//tablename[tmp] = k - 1;
goto iteration_remove_dublicate;
}
else if (el->alias.Lower() == tmp && el->columnList.Len() > 0 && tablename.count(tmp) > 0) {
// remove empty dublicate
int j = tablename[tmp];
listTable.erase(listTable.begin() + j);
//listTable.
goto iteration_remove_dublicate;
}
}
tablename.clear();
//
iteration2:
int s = 0;
complite_element* el;
while (s < listTable.size()) {
el = &listTable[s++];
//r += wxString::Format("[ %s,%s] %s\n", el->table, el->alias, el->columnList);
if (el->table != '@' && el->table.Lower() == t) {
nameTableOut = t;
}
else if (el->alias.Lower() != t) {
continue;
}
else {
nameTableOut = el->table;
if (nameTableOut == "@") nameTableOut = el->alias.Lower();
}
//
if (el->columnList.Len() == 0) {
// transition name
if (tablename.count(nameTableOut) != 0) {
//break;
}
else {
tablename[nameTableOut] = s - 1;
t = nameTableOut;
goto iteration2;
}
}
wxArrayString ar = wxSplit(el->columnList, ',');
wxArrayString rez;
wxString ff;
for (int j = 0; j < ar.GetCount(); j++) {
ff = ar[j].AfterLast('.');
if (astreplace || ff.Lower().StartsWith(f)) rez.Add(ff);
}
r = wxJoin(rez, '\t');
break;
}
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;
int found_index = startIndex;
int start_select_list;
int indexlastID;
union Byte zone { 0 };
wxString lastname;
wxString query_cols_list;
wxArrayString cols, colsfirst;
complite_element el;
wxArrayString objName;
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();
listFunction.clear();
}
while (next_item_no_space(found_index) != -1) {
view_item* vi = &items[found_index];
if (vi->type == comment) {
found_index++;
continue;
}
if (vi->type == keyword) {
union Byte z = zone;
if ((vi->txt.Lower() == "from" && vi->flags != 0) ) {
if (zone.b.select_list) {
if (!lastname.IsEmpty())cols.Add(lastname);
}
zone.b.from = 1; zone.b.select_list = 0;
objName.Clear(); isfunction = false; isskipnext = false; el.columnList = ""; el.alias = ""; el.table = ""; el.level = level; el.startIndex = found_index;
}
if (vi->txt.Lower() == "with") {
zone.b.with = 1; zone.b.skip = 1;
}
if (vi->txt.Lower() == "select") {
zone.b.select_list = 1; zone.b.with = 0; start_select_list = found_index + 1;
isfunction = false;
zone.b.skip = 0;
cols.Clear();
//el.startIndex = found_index + 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;
zone.b.skip = 1;
isskipnext = true;
//objName.Clear();
if (colsfirst.GetCount() == 0) colsfirst = cols;
// после from нам нужно дойти до union и прочих объединений
if (objName.Count() > 0) {
if (isfunction) {
// [ LATERAL ] ( выборка ) [ AS ] псевдоним
// [ LATERAL ] имя_функции ( [ аргумент [, ...] ] ) [WITH ORDINALITY] [[ AS ] псевдоним
el.table = "@";
el.alias = lastname;
}
else {
//[ ONLY ] имя_таблицы [ * ] [ [ AS ] псевдоним
if (objName.GetCount() > 0) el.table = objName[0];
if (objName.GetCount() > 1) el.alias = objName[1];
el.columnList = "";
}
el.endIndex = found_index;
listTable.push_back(el);
el.columnList = ""; el.alias = ""; el.table = ""; el.level = level;
objName.Clear();
}
}
if (z.byte != zone.byte) {
found_index++;
continue;
}
}
else if ((vi->txt == ',' && zone.b.skip == 0) || vi->txt == ')') {
if (zone.b.select_list) {
cols.Add(lastname);
}
else if (zone.b.from) {
close_element_from:
if (!isskipnext) {
if (isfunction) {
// [ LATERAL ] ( выборка ) [ AS ] псевдоним
// [ LATERAL ] имя_функции ( [ аргумент [, ...] ] ) [WITH ORDINALITY] [[ AS ] псевдоним
el.table = "@";
el.alias = lastname;
}
else {
//[ ONLY ] имя_таблицы [ * ] [ [ AS ] псевдоним
if (objName.GetCount() > 0) el.table = objName[0];
if (objName.GetCount() > 1) el.alias = objName[1];
el.columnList = "";
}
el.endIndex = found_index-1;
listTable.push_back(el);
el.startIndex = found_index;
}
}
objName.Clear(); isfunction = false; isskipnext = false; el.columnList = ""; el.alias = ""; el.table = ""; el.level = level;
cols_name = "";
if (vi->txt == ')') {
zone.b.from = 0;
break;
}
if (vi->txt.Lower() == "on") {
isskipnext = true; // skip after on
;
}
}
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 == "::") {
i = found_index;
lastname = "";
int prev = i;
while (next_item_no_space(i) != -1) {
if (items[i].type == separation || (items[i].type == keyword && items[i].txt.Lower() != "as")) break;
if (i - prev > 1) lastname += ' ';
//lastname += items[i].txt;
lastname = items[i].txt;
prev = i;
i++;
}
indexlastID = found_index;
found_index = i - 1;
if (i == -1) break;
}
else {
lastname = vi->txt;
indexlastID = found_index;
}
objName.Add(lastname);
}
}
else if (vi->txt == '(') {
int jump = vi->endlevel;
if (zone.b.select_list) {
_addfunc_in_braket(found_index,jump);
found_index = jump + 1;
continue;
}
if (check_bracket(found_index) == 1) {
// запрос
found_index++;
if (next_item_no_space(found_index) != -1) {
el.startIndex = found_index;
query_cols_list = BuildAutoComplite(found_index, level + 1);
if (zone.b.with) {
el.alias = lastname;
el.table = "@";
if (el.columnList.IsEmpty()) el.columnList = query_cols_list;
//to do check recursive table
listTable.push_back(el);
el.columnList = ""; el.alias = ""; el.table = "";
objName.Clear();
}
else
el.columnList = query_cols_list;
el.level = level;
isfunction = true;
found_index = jump + 1;
el.endIndex = found_index;
continue;
}
}
else {
if (objName.GetCount() == 1) {
if (zone.b.with) {
cols_name = get_list_columns(found_index, zone); // columns name
el.columnList = cols_name;
found_index = jump + 1;
continue;
}
// это функция с аргументами
if (!isfunction) {
_addfunc_in_braket(found_index,jump);
isfunction = true;
found_index = jump + 1;
continue;
}
// определение функции но бех псевдонима
// [ LATERAL ] имя_функции ( [ аргумент [, ...] ] ) AS ( определение_столбца [, ...] )
}
//
cols_name = get_list_columns(found_index, zone);
// get_list_columns(found_index, zone, el);
if (!cols_name.IsEmpty()) {
if (isfunction)
el.table = "@";
else
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;
}
}
else if (vi->txt == '*') {
if (zone.b.select_list) {
//cols.Add('*');
lastname = '*';
}
}
else if (vi->txt == ".*") {
if (zone.b.select_list) {
if (items[found_index - 1].type == name) {
//cols.Add(items[found_index - 1].txt + vi->txt);
lastname = items[found_index - 1].txt + vi->txt;
}
}
}
found_index++;
}
if (zone.b.select_list) {
cols.Add(lastname);
}
if (zone.b.from) { // не было ключевых слов с end_from флагом, значит нужно обработать последний элемент from
if (!isskipnext) {
if (isfunction) {
// [ LATERAL ] ( выборка ) [ AS ] псевдоним
// [ LATERAL ] имя_функции ( [ аргумент [, ...] ] ) [WITH ORDINALITY] [[ AS ] псевдоним
el.table = "@";
el.alias = lastname;
}
else {
//[ ONLY ] имя_таблицы [ * ] [ [ AS ] псевдоним
if (objName.GetCount() > 0) el.table = objName[0];
if (objName.GetCount() > 1) el.alias = objName[1];
el.columnList = "";
}
el.endIndex = found_index;
listTable.push_back(el);
}
};
if (colsfirst.GetCount() > 0) return wxJoin(colsfirst, ',');
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<complite_element> FormatterSQL::ParsePLpgsql(){
int e;
int maxlen=sql.Len();
std::vector<complite_element> listdbobject;
complite_element i;
// Глобальный цикл по тексту
while (lastposition<maxlen) {
int start=lastposition;
e=ParseSql(1);
if (e<0) {
// встретили оператор case
//wxTrap();
lastposition=errorposition;
}
wxString currentsql=sql.substr(start,lastposition-start);
if (currentsql.Len()>0) {
//std::cout << currentsql << std::endl ;
}
// извлечем информацию о таблицах и функциях
BuildAutoComplite(0,0);
//
for(int i=0;i<listTable.size();i++) {
wxString t=listTable[i].table;
// Удалим из функций синонимы таблиц
for(int j=0;j<listFunction.size();j++) {
if (listTable[i].alias==listFunction[j].table)
listFunction[j].table="";
}
if (t=="@") continue;
if (t.Len()>0) {
bool add=true;
for(int k=0;k<i;k++) {
if (listTable[k].table=="@" && listTable[k].alias==t) {
add=false;
break;
}
}
if (add) {
complite_element it;
it.table=t;
wxString lt=t.Lower();
bool ignore=(lt=="alter"||lt=="trigger"||lt=="index");
if (ignore) break;
if (!ignore) {
int itempos=listTable[i].startIndex;
int epos=listTable[i].endIndex;
while (itempos<=epos) {
if (items[itempos].txt==t) break;
itempos++;
}
it.startIndex=items[itempos].srcpos;
listdbobject.push_back(it);
}
}
}
}
// add functions
for(int j=0;j<listFunction.size();j++) {
if (listFunction[j].table.Len()>0) {
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;
}
/// <summary>
/// <c>ParseSql</c> Выполнение разбора текста как SQL выражения
/// </summary>
/// <param name="flags"></param>
/// <returns>Возвращает код ошибки если SQL выражение было не полное или не корректное</returns>
int FormatterSQL::ParseSql(int flags) {
int i = lastposition;
errorposition=-1;
int lhome = 0;
bool str_literal = false;
bool ext = false;
bool newline = false;
int iscomment = 0;
wxRegEx regnumeric("^([0-9]*[.]?[0-9]*([Ee][-+]?[0-9]+)?)|(inf)|(nan)", wxRE_EXTENDED | wxRE_ICASE);
wxRegEx regident("(^[[:alpha:]][[:alnum:]_$]*)", wxRE_EXTENDED | wxRE_ICASE);
wxRegEx regdol("(^[[:alpha:]][[:alnum:]_]*[$])", wxRE_EXTENDED | wxRE_ICASE);
wxChar qt;
wxString cons;
std::stack<int> bracket;
items.clear();
wxString dollarSep;
view_item vi;
bool ex = false;
bool needafterspace = false;
wxChar c;
while (!ex) {
c = '\0';
if (i < sql.length()) c = sql[i++];
else ex = true;
wxChar c2(0);
if (i < sql.length()) c2 = sql[i];
if (iscomment > 0) {
if (ex) {
iscomment = 0;
vi.txt = sql.substr(lhome, i - lhome);
vi.type = comment;
ex = false;
continue;
}
if (iscomment == 1 && (c == '\r' || c == '\n')) {
iscomment = 0;
vi.txt = sql.substr(lhome, i - lhome - 1);
vi.type = comment;
newline = true;
i--;
continue;
}
if (iscomment == 2 && c == '*' && c2 == '/') {
iscomment = 0;
vi.txt = sql.substr(lhome, i - lhome + 1);
vi.type = comment;
i++;
continue;
}
continue;
}
if (vi.type != unknown) {
if (c == '\n' && vi.type == comment && vi.txt[0] == '/') { // \n append comment if exists
vi.txt.append(c);
}
if (vi.type != spaces && newline) {
vi.newline = newline;
newline = false;
}
if (vi.type == name || vi.type == identifier) {
int kj = items.size() - 2;
if (items.size() > 1 && items[kj + 1].txt == "." && (items[kj].type == name || items[kj].type == identifier)) {
// union name.name
items[kj].type = identifier;
items[kj].txt = items[kj].txt + "." + vi.txt;
items[kj].width = items[kj].txt.Len();
//if ("trabopt.value_num" == items[kj].txt) {
// wxLogError("d");
//}
items.resize(kj + 1);
view_item tmp;
vi = tmp;
vi.srcpos = i - 1;
i--;
continue;
}
}
vi.height = 1;
vi.width = vi.txt.Len();
if (vi.width > 0) {
if (vi.txt.Freq('\n') > 0) {
int fnd = 0;
int cur = 0;
int m = 0;
int c = 0;
int d = 0;
while (cur != -1) {
fnd = vi.txt.find('\n', cur);
if (fnd == -1) {
d = vi.txt.Len() - cur;
cur = -1;
}
else {
d = fnd - cur;
cur = fnd + 1;
}
//if (m < d) m = d;
m = d; // last line width
c++;
}
vi.width = m;
vi.height = c;
}
}
else vi.width = 1;
items.push_back(vi);
if (vi.type != spaces && needafterspace) {
view_item tmps;
tmps.type = spaces;
tmps.srcpos = -1;
tmps.width = 1;
items.push_back(tmps);
}
needafterspace = false;
view_item tmp;
vi = tmp;
vi.srcpos = i - 1;
if (ex) continue;
}
if (ex && (str_literal)) continue;
// literal string or identifier
if (str_literal) {
if (c == qt) {
if (c2 == qt) {
// duble quote
cons.Append(c);
cons.Append(c2);
i++;
continue;
}
int k = i;
while (c2 == '\n') {
// verify string const multiline
if (i < sql.length()) c2 = sql[i++]; else break;
}
if (c2 == '\'' && i > k) continue; // multiline string const
// end literal
cons.Append(c);
i = k;
str_literal = false;
ext = false;
vi.txt = cons;
vi.type = qt == '"' ? identifier : literal;
continue;
}
if (ext && c == '\\' && c2 == '\'') {
cons.Append(c);
cons.Append(c2);
i++;
}
cons.Append(c);
continue;
}
// dollar string
if (c == '$') {
wxString tmp = sql.substr(i, 64);
bool matches = regdol.Matches(tmp, 0);
if (matches) {
tmp = regdol.GetMatch(tmp, 0);
int l = tmp.length();
if (tmp[l - 1] == '$') {
dollarSep = "$" + regdol.GetMatch(tmp, 0);
i = i + l;
}
}
else {
if (c2 == '$') {
dollarSep = "$$";
i++;
}
else {
int k = i - 1;
if (wxIsdigit(c2)) {
// bind arg test
while (wxIsdigit(c2)) {
if (i < sql.length()) c2 = sql[i++]; else break;
}
vi.txt = sql.substr(k, i - k-1);
vi.type = bindarg;
i--;
continue;
}
}
}
if (!dollarSep.IsEmpty()) {
int l = dollarSep.Len();
wxString tmp = sql.substr(i);
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 {
// no close dollar
vi.txt = sql.substr(i - l);
i = sql.size();
}
vi.type = literal;
continue;
}
}
if (c == '\'' || c == '"') {
qt = c;
str_literal = true;
vi.srcpos=i-1;
cons = qt;
continue;
}
if ((c == 'e' || c == 'E') && (c2 == '\'')) {
str_literal = true;
cons = c;
cons.Append(c2);
ext = true;
qt = c2;
i++;
continue;
}
// Unicode const
if ((c == 'U' || c == 'u') && (c2 == '&')) {
str_literal = true;
ext = true;
lhome = i - 1;
i++;
cons = c;
cons.Append(c2);
if (i < sql.length()) c = sql[i++];
qt = c;
cons.Append(c);
continue;
}
// comment line
if (c == '-' && c2 == '-') {
iscomment = 1;
lhome = i - 1;
i++;
continue;
}
// comment multiline
if (c == '/' && c2 == '*') {
iscomment = 2;
lhome = i - 1;
i++;
continue;
}
// new line
if (c == '\n') {
vi.type = spaces;
newline = true;
continue;
}
if (c == '\r' && c2 == '\n') {
vi.type = spaces;
newline = true;
i++;
continue;
}
//numeric
if (c == '.' && (wxIsdigit(c2)) || wxIsdigit(c)) {
wxString tmp = sql.substr(i - 1, 30);
bool matches = regnumeric.Matches(tmp, 0);
if (matches) {
vi.txt = regnumeric.GetMatch(tmp, 0);
int l = vi.txt.Len();
vi.type = numeric;
i += l - 1;
continue;
}
}
// spaces
int k = i - 1;
while (c == ' ' || c == '\t') {
if (i < sql.length()) c = sql[i++]; else { i++; break; }
}
if ((i - 1) > k) {
if (items.size() != 0 && items[items.size() - 1].type == spaces) {
// multi space ---> one spaces
if (items[items.size() - 1].srcpos == -1) { // change real space
items[items.size() - 1].srcpos = vi.srcpos;
vi.srcpos = i - 1;
}
}
else
vi.type = spaces;
i--;
continue;
}
// keyword
//
if ((c >= 'a' && c <= 'z') || ((c >= 'A' && c <= 'Z'))) {
unsigned n;
int pos = i - 1;
wxString tmp = sql.substr(pos, 20).Lower();
for (n = 0; n < WXSIZEOF(keyEntities); n++)
{
const KEYWORDEntity& xmlEnt = keyEntities[n];
if (tmp.compare(0, xmlEnt.len, xmlEnt.name) == 0
)
{
if (pos + xmlEnt.len < sql.length()) c2 = sql[pos + xmlEnt.len]; else break;
if (wxIsalpha(c2) || c2 == '_' || c2 == '$' || wxIsxdigit(c2)) continue;
break;
}
}
if (n < WXSIZEOF(keyEntities)) {
tmp = sql.substr(pos, keyEntities[n].len);
int flg = keyEntities[n].flags;
int i2 = items.size();
if (i2 >= 2 && items[items.size() - 2].type == keyword
&& items[items.size() - 1].type == spaces
&& items[items.size() - 2].flags == flg
&& (items[items.size() - 2].flags != special || flg != special)
) {
wxString txt = items[items.size() - 2].txt;
if (!txt.IsEmpty()) txt += " ";
items[items.size() - 2].txt = txt + tmp; // add previous
items[items.size() - 2].width = items[items.size() - 2].txt.Len();
//items[items.size() - 2].flags |= keyEntities[n].flags;
//items.resize(items.size() - 1);
items.erase(items.end() - 1);
}
else {
vi.txt = tmp;
vi.type = keyword;
vi.flags = flg;
if (tmp == "from" && i2 >= 2) { // special case "distinct from" this no "from"
wxString s = items[items.size() - 2].txt;
if (s.Len() >= 8 && s.substr(s.Len() - 8).CmpNoCase("distinct") == 0) {
vi.flags = 0;
}
}
if (strcmp(keyEntities[n].name,"case") == 0) {
bracket.push(items.size());
}
else if (strcmp(keyEntities[n].name,"end") == 0) {
int idx = -1;
if (bracket.size() > 0) {
idx = bracket.top();
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 (strcmp(keyEntities[n].name, "then")== 0 ||
strcmp(keyEntities[n].name,"loop") == 0 ) { // for plpgsql query without ";"
// if ... then
// while ... loop or for .. in query loop
// special break parse query ONLY recurse level == 0
if (bracket.size() == 0) {
i=i+tmp.Len()-1; // skip stop keyword
break;
}
//
}
}
i += tmp.Len() - 1;
continue;
}
}
// bracket
if (c == '(' || c == '[') {
bracket.push(items.size());
vi.txt = c;
vi.type = ::bracket;
if (c == '(') {
// is function
int pp2 = items.size() - 1;
int pp3 = next_item_no_space(pp2, -1);
if (pp3 != -1 && (items[pp3].type == name || items[pp3].type == identifier)) {
items[pp3].flags = isFUNCTION;
}
}
continue;
}
if (c == ')' || c == ']') {
//bracket.push(items.size());
int idx = -1;
if (bracket.size() > 0) {
idx = bracket.top();
bracket.pop();
}
else {
errorposition=i;
return -1; //braket no parent
}
vi.txt = c;
vi.type = ::bracket;
vi.endlevel = idx;
if (idx != -1) items[idx].endlevel = items.size();
continue;
}
// identifier
if ((c >= 'a' && c <= 'z') || ((c >= 'A' && c <= 'Z'))) {
i--;
wxString tmp = sql.substr(i, 64);
bool matches = regident.Matches(tmp, 0);
if (matches) {
tmp = regident.GetMatch(tmp, 0);
vi.txt = tmp;
vi.srcpos=i;
vi.type = name;
i += tmp.Len();
continue;
}
}
// naming arg
if ((c==':') && ((c2 >= 'a' && c2 <= 'z') || ((c2 >= 'A' && c2 <= 'Z')))) {
k = i - 1;
while (wxIsalpha(c2) || c2 == '_') {
if (i < sql.length()) c2 = sql[i++]; else { i++; break; };
}
vi.txt = sql.substr(k, i - k - 1);
vi.type = bindarg;
i--;
continue;
}
if (c == ';') break;
// separat
if (c == ',') {
vi.txt = c;
vi.type = separation;
needafterspace = true;
continue;
}
wxString sepa;
while (true) {
if (c == '-' || c == '+' || c == '.' ||
c == ':' || c == '~' ||
c == '@' || c == '*' || c == '/' ||
c == '=' || c == '!' || c == '#' ||
c == '&' || c == '%' || c == '^' ||
c == '|' || c == '`' || c == '?' ||
c == '>' || c == '<'
)
{
sepa += c;
if (sepa == ".*" || sepa == "::") {
//i++;
break;
}
}
else { i--; break; };
if (i < sql.length()) c = sql[i++]; else break;
}
if (!sepa.IsEmpty()) {
// i=i - (i == sql.length() ? 0: 0);
vi.txt = sepa;
vi.type = separation;
if (items.size() > 0 && items[items.size() - 1].type != spaces) {
// before add space
if (vi.txt != ":" && vi.txt != "." && vi.txt != "::" && vi.txt != ".*") {
view_item vtmp;
vtmp.type = spaces;
vtmp.width = 1;
vtmp.srcpos = -1;
items.push_back(vtmp);
needafterspace = true;
}
}
continue;
}
// no sql command
if (ex) break;
#ifdef _DEBUG
int st=items[items.size()-1].srcpos+items[items.size()-1].txt.Length();
wxString b=sql.substr(st,100);
wxMessageBox(wxString::Format("Bad sql syntax : %s",b));
#endif
errorposition=i;
return -3;
}
// end big loop
if (str_literal) {
errorposition=i;
return -2; // literal no close
}
if (bracket.size() > 0) {
errorposition=i;
return -1; // bracet no close
}
lastposition = i;
return 0;
}
// mode =2 draw position x,y
// mode =1 draw flow
// mode =0 only calc size
wxSize FormatterSQL::best_sizeAndDraw(wxDC& dc, wxPoint& pos, view_item& vi, int mode) {
wxFont f = dc.GetFont();
wxSize sz;
bool draw = true;
if (mode == 2) {
draw = false;
if (pos.x == -1) {
pos.x = vi.x;
pos.y = vi.y;
}
}
dc.SetTextForeground(*wxBLACK);
// dc.SetFont(f.GetBaseFont());
if (vi.type == keyword) {
wxDCFontChanger nfont(dc, f.Bold());
dc.SetTextForeground(wxColour(0, 0, 127));
//wxDCBrushChanger nb(dc, *wxBLUE_BRUSH);
//wxDCPenChanger np(dc, *wxBLUE_PEN);
//dc.SetBrush(*wxGREY_BRUSH);
sz = dc.GetTextExtent(vi.txt);
if ((pos.x + sz.x) > (rect.x + rect.width) && draw) {
pos.x = rect.x + 1;
pos.y = pos.y + sz.y;
}
if (mode > 0) dc.DrawText(vi.txt, pos);
}
else if (vi.type == literal || vi.type == identifier) {
//wxDCPenChanger np(dc, *wxRED_PEN);
dc.SetTextForeground(wxColour(127, 0, 127));
//dc.SetBrush(*wxGREY_BRUSH);
sz = dc.GetMultiLineTextExtent(vi.txt);
if ((pos.x + sz.x) > (rect.x + rect.width) && draw) {
pos.x = rect.x + 1;
pos.y = pos.y + sz.y;
}
if (mode > 0) dc.DrawText(vi.txt, pos);
}
else if (vi.type == spaces) {
sz = dc.GetTextExtent(" ");
if ((pos.x + sz.x) > (rect.x + rect.width) && draw) {
pos.x = rect.x + 1;
pos.y = pos.y + sz.y;
}
}
else if (vi.type == numeric) {
// wxDCPenChanger np(dc, *wxCYAN_PEN);
dc.SetTextForeground(wxColour(0, 127, 127));
sz = dc.GetTextExtent(vi.txt);
if ((pos.x + sz.x) > (rect.x + rect.width) && draw) {
pos.x = rect.x + 1;
pos.y = pos.y + sz.y;
}
if (mode > 0) dc.DrawText(vi.txt, pos);
}
else if (vi.type == bindarg) {
// wxDCPenChanger np(dc, *wxCYAN_PEN);
//dc.SetTextForeground(wxColour(0, 127, 127));
wxDCFontChanger nfont(dc, f.Bold());
sz = dc.GetTextExtent(vi.txt);
if ((pos.x + sz.x) > (rect.x + rect.width) && draw) {
pos.x = rect.x + 1;
pos.y = pos.y + sz.y;
}
if (mode > 0) dc.DrawText(vi.txt, pos);
}
else if (vi.type == comment) {
// wxDCPenChanger np(dc, *wxCYAN_PEN);
dc.SetTextForeground(wxColour(0, 127, 0));
//wxDCFontChanger nfont(dc, f.Bold());
sz = dc.GetMultiLineTextExtent(vi.txt);
if ((pos.x + sz.x) > (rect.x + rect.width) && draw) {
pos.x = rect.x + 1;
pos.y = pos.y + sz.y;
}
if (mode > 0) dc.DrawText(vi.txt, pos);
}
else {
sz = dc.GetTextExtent(vi.txt);
if ((pos.x + sz.x) > (rect.x + rect.width) && draw) {
pos.x = rect.x + 1;
pos.y = pos.y + sz.y;
}
if (mode > 0) dc.DrawText(vi.txt, pos);
if (maxYheightLine < sz.GetHeight()) maxYheightLine = sz.GetHeight();
}
if (draw) {
vi.width = sz.GetWidth();
vi.height = sz.GetHeight();
pos.x += sz.GetWidth();
}
return sz;
}
/// <summary>
/// Получение индекса следующего или предудущего элемента списка отличного от <c>space</c>
/// </summary>
/// <param name="index">Переменная содержащая индекс элемента списка с которого начинаем поиск</param>
/// <param name="direction">Направление перемещения по списку -1 назад, 1 вперед.</param>
/// <returns>индекс найденого элемента или -1 дошли до конца списка. ВАЖНО: в переменной <c>index</c> будет содержаться найденное значение.</returns>
int FormatterSQL::next_item_no_space(int& index, int direction) {
bool f = true;
while (index >= 0 && index < items.size()) {
if (items[index].type != spaces) {
f = false;
break;
}
index += direction;
}
if (f) return -1;
return index;
}
int FormatterSQL::get_prev_value(int indx, wxString keyword) {
bool f = true;
int indxs = indx;
while (--indx >= 0) {
if (items[indx].endlevel != -1 && items[indx].endlevel > indxs) { // start bracet = stop find
break;
}
if (items[indx].endlevel != -1 && items[indx].endlevel < indx) {
indx = items[indx].endlevel; //
continue;
}
if (items[indx].type == type_item::keyword && items[indx].txt.Lower() == keyword) {
f = false;
break;
}
}
if (f) return -1;
return indx;
}
/// <summary>
/// Проверка что в скобках подзапрос
/// </summary>
/// <param name="index"> индекс элемента типа <c>bracket</c> созначениме '(' </param>
/// <returns>1 если подзапрос
/// <p>
/// 0 иначе</p></returns>
int FormatterSQL::check_bracket(int index) {
int s = index;
wxChar b = items[s].txt[0];
s++;
if (b == '(') {
while (next_item_no_space(s) != -1) {
if (items[s].type == keyword && items[s].flags & newLineComma) { // only select update delete insert
// subquery
return 1;
}
if (items[s].type == comment) {
s++;
continue;
}
break;
}
}
return 0;
}
wxSize FormatterSQL::max_width_size(int index) {
int e_idx = index;
int s_idx = items[e_idx].endlevel;
int max_w = 0;
wxSize sz(0, 0);
if (s_idx != -1) {
if (s_idx > e_idx) {
int t = s_idx;
s_idx = e_idx;
e_idx = t;
}
int min_y = 9999999;
int max_y = 0;
int min_x = 9999999;
int max_x = 0;
while (++s_idx < e_idx) {
//max_w += items[s_idx].width;
if (min_y > items[s_idx].y) min_y = items[s_idx].y;
if (max_y < (items[s_idx].y + items[s_idx].height)) max_y = items[s_idx].y + items[s_idx].height;
if (min_x > items[s_idx].x) min_x = items[s_idx].x;
if (max_x < (items[s_idx].x + items[s_idx].width)) max_x = items[s_idx].x + items[s_idx].width;
}
sz.x = max_x - min_x;
sz.y = max_y - min_y;
}
return sz;
}
wxPoint FormatterSQL::align_level(int start_i, int level, int Xpos, int Ypos, int flag) {
int s = start_i;
int xstart = Xpos;
int padd = flag & isCASE ? 0 : pad;
bool iswith = false;
bool empty_line = true;
neededNewLine.x = -1;
neededNewLine.y = -1;
bool isOneNL = false;
while (s < items.size()) {
view_item* vi = &items[s++];
if (vi->type == unknown) continue;
int fl = vi->flags;
wxPoint p(0, 0);
wxString txt = wxString(vi->txt);
if (vi->type == keyword && txt == "with") iswith = true;
if ((vi->txt.Lower() == "dblink")) {
// neededNewLine.y = -1;
}
if (s == 2511) {
p.x = p.x;
}
if (fl & newLineComma) {
flag = fl;
}
else if (fl & HorizontalAlign) {
int iprev = get_prev_value(s - 1, txt.Lower());
if (iprev != -1) {
view_item v = items[iprev];
if (v.y < Ypos && v.x >= Xpos) {
Xpos = v.x;
}
}
}
else if (fl & newLineXPrevValue) {
int iprev = get_prev_value(s - 1, txt.Lower());
if (txt.Lower() == "and") {
int ibetween = get_prev_value(s - 1, "between"); // exclude between ... and
int ion = get_prev_value(s - 1, "on"); // exclude on
if (ibetween != -1 && ion < ibetween) {
int nx = items[ibetween].x + items[ibetween].width - vi->width;
neededNewLine = wxPoint(nx, Ypos + maxYheightLine);
iprev = -1;
}
if (ion > iprev && iprev != -1) iprev = -1;
ion = get_prev_value(s - 1, "where"); // exclude on
if (ion > iprev && iprev != -1) iprev = -1;
}
if (iprev != -1) {
view_item v = items[iprev];
//if (Ypos - v.y == 0)
{
neededNewLine = wxPoint(v.x, Ypos + maxYheightLine);
}
}
}
else if (vi->type == bracket || (vi->endlevel != -1)) {
// recursion
if ((vi->txt.Lower() == "end"
|| (vi->endlevel < s)) && level > 0) {
if (items[start_i - 1].endlevel == (s - 1)) {
// this subquey
// exit recursion bracet
return wxPoint(Xpos, Ypos);
}
}
else
//if (check_bracket(s - 1) > 0)
{
// subquery
bool subq = check_bracket(s - 1) > 0;
if ((iswith && subq)) {
Xpos = xstart;
Ypos += maxYheightLine;
}
vi->x = Xpos;
vi->y = Ypos;
Xpos += vi->width;
int nl = 0; //(subq ? maxYheightLine : 0)
int flag_in = 0;
if (fl & isCASE) flag_in = fl;
if (flag & newLineBracet) flag_in |= newLineBracet;
if (flag & isCASE &&
fl & isCASE) { // nested case 2 level
Xpos = xstart + casepad;
Ypos += maxYheightLine;
vi->x = Xpos;
vi->y = Ypos;
Xpos += vi->width;
//Ypos = vi->y;
}
bool ex = false;
while (!ex) {
p = align_level(s, level + 1, Xpos, Ypos + nl, flag_in); // new line
// need draw close bracet
ex = true;
if (subq || fl & isCASE) {
//Xpos = vi->x; // vertical aling bracket
//Ypos = p.y + maxYheightLine; // new line close braket
neededNewLine = wxPoint(vi->x, p.y + maxYheightLine);
}
else {
wxSize sz_br = max_width_size(s - 1);
if ((flag & newLineBracet) && sz_br.x > max_exp_bracet_width) {
isOneNL = true;
}
if (sz_br.x > rect.width && ((flag & newLineBracet) == 0)) {
if (start_i > 0 && (items[start_i - 1].txt[0] == '(') && p.y == items[start_i - 1].y)
return p;
if (!(flag_in & newLineBracet)) {
flag_in = newLineBracet;
ex = false;
continue;
}
}
if (sz_br.y > maxYheightLine /*&& !isOneNL*/) { // 2 > line
//p.x = vi->x; // vertical aling bracket
//p.y = p.y + maxYheightLine; // new line close braket
neededNewLine = wxPoint(vi->x, p.y + maxYheightLine);
}
else {
Xpos = p.x;
Ypos = p.y;
}
}
}
p.x = 0; p.y = 0;
s = vi->endlevel;
vi = &items[s++];
fl = vi->flags;
if (fl & isCASE) fl = 0;
}
}
if (p.x == 0 && p.y == 0) {
if ((vi->txt.Lower() == "from" ||
vi->txt.Lower() == "order"
) && ((flag & newLineComma) == 0)) // for function args
fl = none;
if (flag & newLineComma && vi->type == separation && vi->txt[0] == ',') {
neededNewLine = wxPoint(xstart + padd, Ypos + maxYheightLine);
if (s < items.size() && items[s].type == spaces) items[s].type = unknown; // disable next space
}
else if (fl & new_line_align_next) {
if (neededNewLine == wxPoint(-1, -1)) {
neededNewLine = wxPoint(xstart + padd, Ypos + maxYheightLine);
}
}
else if (fl & new_line_align_no_pad) {
if (neededNewLine == wxPoint(-1, -1) && Ypos != 0) {
neededNewLine = wxPoint(xstart, Ypos + maxYheightLine);
}
}
if (vi->type != spaces && neededNewLine != wxPoint(-1, -1)) {
Xpos = neededNewLine.x;
Ypos = neededNewLine.y;
neededNewLine = wxPoint(-1, -1);
};
if ((Xpos + vi->width) >= (rect.width + rect.x)) { // long line
//Xpos = xstart;
//Ypos += maxYheightLine;
}
vi->x = Xpos;
vi->y = Ypos;
Xpos += vi->width;
if (vi->height > maxYheightLine) Ypos = Ypos + vi->height - maxYheightLine;
if (vi->type == comment && txt[0] == '-') {
neededNewLine = wxPoint(xstart + padd, Ypos + maxYheightLine);
}
if (isOneNL) {
neededNewLine = wxPoint(xstart, Ypos + maxYheightLine);
isOneNL = false;
}
}
}
return wxPoint(0, 0);
}
void FormatterSQL::Formating(wxDC& dc, wxRect re, bool isTest) {
dc.SetBrush(*wxWHITE_BRUSH);
dc.DrawRectangle(re);
maxYheightLine = 0;
rect.x = re.x;
rect.y = re.y;
rect.width = re.width;
rect.height = re.height;
max_exp_bracet_width = 200;
pad = 16;
casepad = 8;
wxPoint p(rect.x + 1, 0);
// calc size items
for (auto& vv : items) {
wxSize sz = best_sizeAndDraw(dc, p, vv, 0);
}
//
align_level(0, 0, rect.x, rect.y, 0);
dc.SetBrush(*wxWHITE_BRUSH);
dc.DrawRectangle(re);
wxSize sz;
int xmax = rect.x + rect.width;
int deltax = 0,deltay=0;
for (auto& vv : items) {
p.x = -1;
if (vv.type == unknown) continue;
if (vv.x + vv.width > xmax) {
if (deltax == 0 || (rect.x + vv.width + deltax > xmax)) { deltax = 1; deltay += vv.height; }
}
else
deltax = 0;
if (deltay != 0) {
p.y = vv.y+deltay;
if (deltax == 0)
p.x = vv.x;
else
p.x = rect.x + deltax;
}
sz = best_sizeAndDraw(dc, p, vv, 2);
if (vv.x + vv.width > xmax && deltax>0) {
deltax += vv.width;
}
}
}
// re - (0,0,width simbol max,height max)
wxString FormatterSQL::Formating(wxRect re) {
rect.x = re.x;
rect.y = re.y;
rect.width = re.width;
rect.height = re.height;
maxYheightLine = 1;
max_exp_bracet_width = 100;
pad = 2;
casepad = 1;
align_level(0, 0, rect.x, rect.y, 0);
// calc string items
wxString str;
int cy = 0;
int cx = 0;
for (auto& vv : items) {
if (vv.type == unknown) continue;
if (cy < vv.y) {
wxString r('\n', vv.y - cy);
str.append(r);
cy += vv.y - cy;
cx = 0;
}
{
int d = vv.x - cx;
if (d > 0) {
wxString r(' ', d);
str.append(r);
cx += d;
}
if (vv.txt.Len() > 0) str.append(vv.txt); else str.append(' ');
if (vv.height > maxYheightLine) {
cy = cy + vv.height - maxYheightLine;
cx = vv.width;
}
else cx += vv.width;
}
}
return str;
}