Added new features autocomplite.

1. Добавлена подстановка соединений таблиц(и представлений) по их FK.
   Подстановка работает в двух вариантах:
   1.1 После ключевого слова ON:самая правая таблица соединяется с любой левой.
   1.2 После ключевого слова WHERE AND OR все таблицы соединяются со всеми.
2. Дополнение условия соединения после символа = .
   Представления можно соединить только если поле представления является полем таблицы.
3. Стандартное автодополнение теперь выдаёт список таблиц и представление после JOIN.
This commit is contained in:
lsv 2025-02-10 11:01:46 +05:00
parent b7b911c93b
commit 21ee30844a
10 changed files with 1873 additions and 916 deletions

View file

@ -29,6 +29,7 @@
#include "utils/popuphelp.h"
#include "utils/FormatterSQL.h"
#include "utils/dlgTransformText.h"
#include "utils/TableColsMap.h"
wxString ctlSQLBox::sqlKeywords;
static const wxString s_leftBrace(_T("([{"));
@ -239,6 +240,7 @@ void ctlSQLBox::Create(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons
AutoCompSetIgnoreCase(true);
AutoCompSetFillUps(wxT(" \t"));
AutoCompSetDropRestOfWord(true);
AutoCompSetMaxHeight(10);
SetEOLMode(settings->GetLineEndingType());
}
@ -1748,7 +1750,12 @@ void ctlSQLBox::OnAutoComplete(wxCommandEvent &rev)
else
tab_ret = tab_complete(what.mb_str(wxConvUTF8), spaceidx + 1, what.Len() + 1, m_database);
wxString wxRet;
if ((tab_ret == NULL || tab_ret[0] == '\0')&&(what.Right(1)>' ')){
if (tab_ret != NULL) {
wxRet = wxString(tab_ret, wxConvUTF8);
free(tab_ret);
}
if ((wxRet.IsEmpty())){ // my autocomplite
int extflag = 0;
auto [s,e] = SelectQuery(pos);
wxString sql = GetTextRange(s, e);
FSQL::FormatterSQL f(sql);
@ -1763,10 +1770,23 @@ void ctlSQLBox::OnAutoComplete(wxCommandEvent &rev)
if (vi.type == FSQL::spaces) {
ItemPos--;
f.GetItem(ItemPos, vi);
extflag += TableColsMap::Flag::NOT_ADD_FIRST_SPACE;
}
wxString leftexp;
if (vi.type == FSQL::separation && vi.txt == '=') {
f.GetItem(ItemPos, vi);
ItemPos--;
if (f.next_item_no_space(ItemPos, -1) != -1) {
f.GetItem(ItemPos, vi);
if (vi.type == FSQL::identifier || vi.type == FSQL::name) {
leftexp = vi.txt;
}
else return;
};
}
wxString field;
bool ast = false;
if (vi.type == FSQL::separation) {
if (vi.type == FSQL::separation && !CHKFLAG(extflag, TableColsMap::Flag::NOT_ADD_FIRST_SPACE)) {
ast=vi.txt == ".*";
while (f.GetItem(--ItemPos, vi)) {
if (vi.type == FSQL::identifier || vi.type == FSQL::name) {
@ -1776,9 +1796,32 @@ void ctlSQLBox::OnAutoComplete(wxCommandEvent &rev)
if (vi.srcpos != -1) break;
};
}
else if (vi.type == FSQL::identifier) {
else if (vi.type == FSQL::identifier && !CHKFLAG(extflag, TableColsMap::Flag::NOT_ADD_FIRST_SPACE && leftexp.IsEmpty())) {
field = vi.txt;
}
else if (vi.type == FSQL::keyword || !leftexp.IsEmpty()) {
// where, on ,and , ...
wxArrayString tn, an;
int ccc=f.GetTableListBeforePosition(ItemPos,tn,an);
if (ccc > 1) {
// decode view select field
TableColsMap tmaps;
int flag = TableColsMap::Flag::ALL_LEFT_TO_RIGHT | TableColsMap::Flag::USE_TRANSIT_FK;
if (leftexp.IsEmpty()) {
if (vi.txt.Lower() == "where" || vi.txt.Lower() == "and" || vi.txt.Lower() == "or") flag = TableColsMap::Flag::SEQUENCE_LIST_TABLE | TableColsMap::Flag::USE_TRANSIT_FK;
} else flag = TableColsMap::Flag::SEQUENCE_LIST_TABLE | TableColsMap::Flag::USE_TRANSIT_FK;
flag |= extflag;
wxString list=tmaps.AddTableList(m_database, tn, an, (TableColsMap::Flag) flag,leftexp);
if (!list.IsEmpty()) {
int l2 = 0;
AutoCompShow(l2, list);
}
//
//wxString r = wxJoin(tn, '\t');
}
return;
}
if (!field.IsEmpty()) {
wxString lf;
wxString tabn;
@ -1820,6 +1863,30 @@ void ctlSQLBox::OnAutoComplete(wxCommandEvent &rev)
r=wxJoin(sort, '\t');
AutoCompShow(l2, r);
}
return;
}
if (vi.type == FSQL::name) {
field = vi.txt;
// for any name found table
bool isok = false;
while (f.GetItem(ItemPos--, vi)) {
if (vi.endlevel != -1 && vi.endlevel < ItemPos) ItemPos = vi.endlevel;
if (vi.type == FSQL::keyword && vi.txt.Lower() == "from") { isok = true; break; }
if (vi.type == FSQL::keyword && vi.txt.Lower() == "where") { break; }
if (vi.type == FSQL::keyword && vi.txt.Lower() == "group") { break; }
}
if (isok) {
what = "from " + field;
spaceidx = what.Find(' ');
tab_ret = tab_complete(what.mb_str(wxConvUTF8), spaceidx + 1, what.Len() + 1, m_database);
if (tab_ret != NULL) {
wxString wxRet;
wxRet = wxString(tab_ret, wxConvUTF8);
bool empty = tab_ret[0] == '\0';
free(tab_ret);
if (!empty) AutoCompShow(what.Len() - spaceidx - 1, wxRet);
}
}
}
return;
}
@ -1932,8 +1999,6 @@ void ctlSQLBox::OnAutoComplete(wxCommandEvent &rev)
spaceidx = -1;
//return; /* No autocomplete available for this string */
} else {
wxRet = wxString(tab_ret, wxConvUTF8);
free(tab_ret);
}
// Switch to the generic list control. Native doesn't play well with
// autocomplete on Mac.

File diff suppressed because it is too large Load diff

View file

@ -536,6 +536,8 @@ wxString ctlSQLResult::AutoColsPlot(int flags,frmQuery* parent) {
wxArrayString leg;
wxArrayInt colsY;
wxString rez="Draw plot";
wxString sss;
std::vector<long> typeCols;
if (IsSelection()) {
unsigned int i;
int col;
@ -551,13 +553,20 @@ wxString ctlSQLResult::AutoColsPlot(int flags,frmQuery* parent) {
{
for (col = topLeft[i].GetCol(); col <= bottomRight[i].GetCol(); col++) {
//clearCellData(row, col);
wxString aa = wxString::Format(",%d,", col);
if (sss.Find(aa) != -1) continue;
sss += aa;
cols.Add(col);
typeCols.push_back(colTypClasses.Item(col));
}
}
}
wxArrayInt cls = GetSelectedCols();
for (i = 0; i < cls.Count(); i++) {
if (cols.Index(cls[i])== wxNOT_FOUND) cols.Add(cls[i]);
if (cols.Index(cls[i]) == wxNOT_FOUND) {
cols.Add(cls[i]);
typeCols.push_back(colTypClasses.Item(cls[i]));
}
// for (row = 1; row < GetNumberRows(); row++) {
// clearCellData(row, cols[i]);
// }
@ -575,10 +584,10 @@ wxString ctlSQLResult::AutoColsPlot(int flags,frmQuery* parent) {
int legC = -1;
int xC = -1;
//cols = GetSelectedCols();
if (cols.Count() == 2) {
legC = cl1; idx++;
if (cols.Count() == 2 && typeCols[idx]!= PGTYPCLASS_NUMERIC) {
legC = cl1; idx++; // 1 cols = legend
cl1 = cols[idx];
if (colTypClasses.Item(cl1) != PGTYPCLASS_NUMERIC) {
if (typeCols[idx] != PGTYPCLASS_NUMERIC) {
wxMessageBox("The number of selected column 2 needed Number type\nExample: LY", "Plot");
return "";
}
@ -604,17 +613,29 @@ wxString ctlSQLResult::AutoColsPlot(int flags,frmQuery* parent) {
}
if (cols.Count() < 3) {
wxMessageBox("The number of selected columns must be more than 1\nExample: LXY or XYYYY...", "Plot");
return "";
// wxMessageBox("The number of selected columns must be more than 1\nExample: LXY or XYYYY...", "Plot");
// return "";
}
}
else {
wxMessageBox("The number of selected columns must be more than 0\nExample: LXY or Y or XYYYY...", "Plot");
return "";
}
int idx = 0;
int cl1 = cols[idx];
int legC = -1;
int xC = -1;
int typeAxisX = mpX_DATETIME;
// Leg col
if (colTypClasses.Item(cl1) == PGTYPCLASS_STRING) { legC = cl1; idx++; }
if (typeCols[idx] == PGTYPCLASS_STRING) {
legC = cl1;
idx++;
if (cols.Count() < 2) {
wxMessageBox("Column 2 must be numeric\nExample: LY or LXY", "Plot");
return "";
}
}
cl1 = cols[idx];
// X col
wxString xA, yA;
@ -622,6 +643,16 @@ wxString ctlSQLResult::AutoColsPlot(int flags,frmQuery* parent) {
if (colTypClasses.Item(cl1) == PGTYPCLASS_DATE) { xC = cl1; idx++; }
else if (colTypClasses.Item(cl1) == PGTYPCLASS_NUMERIC) { xC = cl1; typeAxisX = mpX_NORMAL; idx++; }
if (xC == -1) { wxMessageBox("The value type of column X must be a date or a number", "Plot"); return ""; }
if (cols.GetCount() == 1 && typeCols[0]!= PGTYPCLASS_NUMERIC) {
wxMessageBox("The value type of column Y must be a number", "Plot");
return "";
}
if (cols.GetCount() == 1 && typeCols[0] == PGTYPCLASS_NUMERIC) {
// only Y
xC = -1;
idx = 0;
xA = "NumRow";
}
// Y cols
for (size_t col = idx; col < cols.Count(); col++)
{
@ -695,6 +726,7 @@ wxString ctlSQLResult::AutoColsPlot(int flags,frmQuery* parent) {
int fmttype = -1;
wxDateTime dt;
bool first = true;
if (cols.GetCount() == 1) first = false;
for (size_t col = 0; col < colsY.Count(); col++)
{
lg = leg[col];
@ -726,6 +758,10 @@ wxString ctlSQLResult::AutoColsPlot(int flags,frmQuery* parent) {
y.push_back(yv);
}
if (cols.GetCount() == 1) {
double nrows = 1;
for (int i = 0; i < numRows; i++) x.push_back(nrows++);
}
frame->AddSeries(lg, x, y,lbar);
y.clear();
first = false;

View file

@ -131,6 +131,7 @@ namespace FSQL {
wxString columnList;
int startIndex = -1;
int endIndex = -1;
int level = 0;
};
struct bits {
unsigned int from : 1;
@ -154,6 +155,11 @@ namespace FSQL {
wxString BuildAutoComplite(int startIndex, int level);
wxString GetListTable(int cursorPos);
wxString GetColsList(wxString what, wxString& listfieldOut, wxString& nameTableOut);
/// <summary>
/// Возращает количество таблиц слева от курсора и заполняет их имена и псевдонимы.
///
/// </summary>
int GetTableListBeforePosition(int positem, wxArrayString& listtable, wxArrayString& listalias);
//
int ParseSql(int flags);
wxString printParseArray();
@ -161,13 +167,14 @@ namespace FSQL {
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);
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);
int next_item_no_space(int& index, int direction = 1);
wxSize max_width_size(int index); // index bracet
wxSize best_sizeAndDraw(wxDC& dc, wxPoint& pos, FSQL::view_item& vi, int mode);
int maxYheightLine = 0;

View file

@ -0,0 +1,192 @@
#pragma once
class Table;
class Cols
{
public:
wxString name;
int num;
// Для v - ссылка view/table текущего уровня (самый нижний уровень).
// Для r - сыылка на FK таблицу
Table *linkTable=NULL;
int linknumcol;
wxString linkOid;
bool pk = false;
//(самый нижний уровень).
Table* relTable = NULL;
int relcol = 0;
bool operator< (const Cols& e) const
{
return (name < e.name);
}
bool operator==(const Cols& otherPoint) const
{
if (this->name == otherPoint.name ) return true;
else return false;
}
struct HashFunction
{
size_t operator()(const Cols& point) const
{
size_t xHash = std::hash<wxString>()(point.name);
//size_t yHash = std::hash<wxString>()(point.y) << 1;
return xHash;
}
};
};
class Table
{
public:
Table(const wxString &tablename);
Table() {};
const wxString& GetName(){ return name; };
const wxString& GetSchema() { return schema; };
const wxString& GetAlias() { return alias; };
void SetAlias(const wxString str) { alias=str; };
const wxString& GetOID() { return oid; };
const int GetColsCount() { return cols.size(); };
Cols& operator[](unsigned index) { return cols[index]; }
void SetCol(int index,Cols & valcol){ cols[index]=valcol; }
char GetType() { return type; };
void Set(wxString kind,wxString sch,wxString toid, wxString tname);
void AddColumn(wxString ncol, wxString colname, wxString oidTable, wxString ncolTable);
bool operator< (const Table& e) const
{
return (schema < e.schema)||(schema == e.schema && name < e.name);
}
bool operator==(const Table& otherPoint) const
{
if (this->name == otherPoint.name && this->schema== otherPoint.schema) return true;
else return false;
}
struct HashFunction
{
size_t operator()(const Table& point) const
{
size_t xHash = std::hash<wxString>()(point.name);
size_t yHash = std::hash<wxString>()(point.schema) << 1;
return xHash ^ yHash;
}
};
private:
void make_name(const wxString& tablename);
wxString name;
wxString schema;
wxString alias;
char type=0; // pg_class.relkind
wxString oid;
//std::unordered_set<Cols,Cols::HashFunction> cols;
std::vector<Cols> cols;
};
/// <summary>
/// Связка двух таблиц по колонкам
/// </summary>
class LinkTableFK
{
public:
Table *parent;
Table *child;
std::vector<int> colsp;
std::vector<int> colsc;
};
typedef struct tab_col_struct {
Table* t; int column;
bool operator< (const tab_col_struct& e) const
{
return (t < e.t) || (t == e.t && column < e.column);
}
bool operator==(const tab_col_struct& otherPoint) const
{
if (t == otherPoint.t && column == otherPoint.column) return true;
else return false;
}
} tab_col_struct;
typedef struct tab_tab_struct {
Table* t;
Table* t2;
bool operator< (const tab_tab_struct& e) const
{
return (t < e.t) || (t == e.t && t2 < e.t2);
}
} tab_tab_struct;
typedef struct fk_full_struct {
tab_col_struct left;
tab_col_struct right;
bool operator< (const fk_full_struct& e) const
{
return (left < e.left) || (left == e.left && right < e.right);
}
} fk_full_struct;
class TableColsMap
{
public:
enum Flag {
ALL_LEFT_TO_RIGHT = 1,
SEQUENCE_LIST_TABLE = 2,
USE_TRANSIT_FK = 4,
NOT_ADD_FIRST_SPACE=8
};
#define CHKFLAG(va,par) (((va) & (par))==(par))
#ifdef DEBUG
static void TEST() {
Table v1("\"schema\".table");
Table v2("\"schema\".\"table\"");
Table v3("schema.table");
Table v4("schema.\"table\"");
Table v5("\"table\"");
Table v6("table");
Table e1("sc.table.t");
Table e2("\"\".table");
Table e3("\".\".table");
}
#endif // DEBUG
TableColsMap() { regaction.Compile(
R"((?x)
\{TARGETENTRY.*?
(\{ ([^{}]++|(?1))* \})
\s+:resno\s(\d+)\s
:resname\s(\S+)\s.*?
:resorigtbl\s(\d+)\s.*?
:resorigcol\s(\d+)(\s:)
)",wxRE_EXTENDED);
};
void checkDBconn(pgConn *dbconn);
/// <summary>
/// Добавить таблицы для поиска ссылок.
/// </summary>
/// <param name="dbconn">Соединие к БД</param>
/// <param name="tables">список таблиц</param>
/// <param name="alias">список синонимов</param>
/// <returns>true если успешно добавлены</returns>
wxString AddTableList(pgConn* dbconn, const wxArrayString& tables, const wxArrayString& alias, const TableColsMap::Flag flag, const wxString& leftexp);
void MapViewColToRelCol(const TableColsMap::Flag flag);
Table* GetRelTableForViewCol(const wxString oid, int ncolview, int& outrelcol);
Table* GetTablebyOID(const wxString oid);
Table* GetTableByName(const wxString& sch, const wxString& tname);
void BuildMapTableColumnsToSQLexp(Table* reltab, const wxString& alias, std::map<tab_col_struct, wxString>& map);
wxString MapTableToTable(std::map<tab_col_struct, wxString>& maplefttables, std::map<tab_col_struct, wxString>& maprighttable, const TableColsMap::Flag flag, const wxString& leftexp);
~TableColsMap();
private:
void getDatabaseViews();
void getDatabaseTables(const TableColsMap::Flag flag);
tab_col_struct search_link(tab_col_struct start, tab_col_struct chknode, std::set<fk_full_struct>& traz, int level);
void Clear();
pgConn* db = NULL;
//std::map<Table *, std::vector<LinkTableFK>> tablechild_fk;
std::multimap<tab_col_struct, tab_col_struct> all_fk_index; // {parent_t, n_col}={ftable, fcol}
//std::map<Table *, wxString> alias;
wxRegEx regaction;
std::map<wxString, Table*> oids;
wxString whereexp;
};

View file

@ -1088,6 +1088,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
</PrecompiledHeader>
</ClCompile>
<ClCompile Include="utils\TableColsMap.cpp" />
<ClCompile Include="utils\utffile.cpp" />
<ClCompile Include="debugger\ctlMessageWindow.cpp" />
<ClCompile Include="debugger\ctlResultGrid.cpp" />
@ -1615,6 +1616,7 @@
<ClInclude Include="include\utils\json\jsonval.h" />
<ClInclude Include="include\utils\json\jsonwriter.h" />
<ClInclude Include="include\utils\json\json_defs.h" />
<ClInclude Include="include\utils\TableColsMap.h" />
<ClInclude Include="include\utils\sshTunnel.h" />
<ClInclude Include="include\version.h" />
<ClInclude Include="include\utils\csvfiles.h" />

View file

@ -844,6 +844,9 @@
<ClCompile Include="utils\utffile.cpp">
<Filter>utils</Filter>
</ClCompile>
<ClCompile Include="utils\TableColsMap.cpp">
<Filter>utils</Filter>
</ClCompile>
<ClCompile Include="debugger\dbgController.cpp">
<Filter>debugger</Filter>
</ClCompile>
@ -3562,6 +3565,9 @@
<ClInclude Include="include\utils\sshTunnel.h">
<Filter>include\utils</Filter>
</ClInclude>
<ClInclude Include="include\utils\TableColsMap.h">
<Filter>include\utils</Filter>
</ClInclude>
<ClInclude Include="include\schema\edbResourceGroup.h">
<Filter>include\schema</Filter>
</ClInclude>

View file

@ -76,6 +76,39 @@ wxString FormatterSQL::GetListTable(int cursorPos) {
}
return r;
}
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 = "";
@ -181,7 +214,7 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) {
wxString cols_name;
bool isfunction = false;
bool isskipnext = false;
el.columnList = ""; el.alias = ""; el.table = "";
el.columnList = ""; el.alias = ""; el.table = ""; el.level = level;
if (level == 0) listTable.clear();
while (next_item_no_space(found_index) != -1) {
view_item* vi = &items[found_index];
@ -196,7 +229,7 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) {
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 = "";
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;
@ -209,7 +242,6 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) {
//el.startIndex = found_index + 1;
}
if (vi->txt.Lower().Find("join") > -1) {
goto close_element_from;
}
if (vi->txt.Lower() == "on") {
@ -237,8 +269,9 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) {
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.columnList = ""; el.alias = ""; el.table = ""; el.level = level;
objName.Clear();
}
}
@ -267,11 +300,14 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) {
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 = "";
objName.Clear(); isfunction = false; isskipnext = false; el.columnList = ""; el.alias = ""; el.table = ""; el.level = level;
cols_name = "";
if (vi->txt == ')') {
zone.b.from = 0;
@ -323,6 +359,7 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) {
// запрос
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;
@ -336,8 +373,11 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) {
}
else
el.columnList = query_cols_list;
el.level = level;
isfunction = true;
found_index = jump + 1;
el.endIndex = found_index;
continue;
}
}
@ -405,7 +445,6 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) {
// [ LATERAL ] имя_функции ( [ аргумент [, ...] ] ) [WITH ORDINALITY] [[ AS ] псевдоним
el.table = "@";
el.alias = lastname;
}
else {
//[ ONLY ] имя_таблицы [ * ] [ [ AS ] псевдоним
@ -413,6 +452,7 @@ wxString FormatterSQL::BuildAutoComplite(int startIndex, int level) {
if (objName.GetCount() > 1) el.alias = objName[1];
el.columnList = "";
}
el.endIndex = found_index;
listTable.push_back(el);
}

600
utils/TableColsMap.cpp Normal file
View file

@ -0,0 +1,600 @@
#include "pgAdmin3.h"
#include "utils/TableColsMap.h"
Table::Table(const wxString &tablename) {
make_name(tablename);
}
void Table::make_name(const wxString& tablename) {
if (tablename.length() > 0) {
size_t pospoint = tablename.Find('.');
size_t pos = 0;
std::vector<wxString> names;
while (pos < tablename.length()) {
size_t posstart = tablename.find('"',pos);
size_t posend = -1;
if (posstart>=0) posend=tablename.find('"', posstart + 1);
wxString tmp2;
if (pospoint != -1 && pospoint < posstart) posstart = -1;
if (posstart != -1 && posend != -1) {
tmp2 = tablename.substr(pos + 1, posend - pos - 1);
names.push_back(tmp2);
pos = posend+1;
if (pos == pospoint) {
//"schema".table || "schema"."table"
pos++;
pospoint = -1;
continue;
}
else {
// "schema"
continue;
}
}
else {
if (pospoint != -1) {
// scema.table || scema."table"
tmp2 = tablename.substr(pos, pospoint - pos);
names.push_back(tmp2.MakeLower());
pos = pospoint + 1;
pospoint = -1;
}
else {
tmp2 = tablename.substr(pos);
names.push_back(tmp2.MakeLower());
pos = tablename.length();
}
}
}
if (names.size() > 0) {
int pp = 0;
if (names.size() == 2) schema = names[pp++];
name = names[pp];
}
}
}
void TableColsMap::Clear() {
for (auto& e : oids) {
Table* t = e.second;
delete t;
}
oids.clear();
}
void TableColsMap::checkDBconn(pgConn* dbconn) {
if (db == NULL || dbconn != db) {
Clear();
db = dbconn;
}
//alias.clear();
}
void TableColsMap::BuildMapTableColumnsToSQLexp(Table *reltab, const wxString &alias, std::map<tab_col_struct, wxString> &map) {
if (reltab != NULL) {
Table tmp = *reltab;
for (int c = 0; c < reltab->GetColsCount(); c++) {
Table* rel;
if (tmp.GetType() == 'v') rel = tmp[c].relTable; else rel = reltab;
if (rel == NULL) continue;
int ncol = tmp[c].relcol;
if (ncol < 1) continue;
tab_col_struct fnd = { rel,ncol };
wxString sqlname = tmp[c].name;
if (!alias.IsEmpty()) sqlname = alias + "." + sqlname;
map.insert({fnd,sqlname});
}
}
return;
}
TableColsMap::~TableColsMap()
{ Clear(); }
wxString TableColsMap::AddTableList(pgConn* dbconn, const wxArrayString& tables, const wxArrayString& alias, const TableColsMap::Flag flag, const wxString &leftexp) {
checkDBconn(dbconn);
Table right;
wxString dbl = "";
wxString onlytab = "";
for (int i = tables.GetCount() - 1; i >= 0; i--) {
Table t(tables[i]);
if (i == 0) right = t;
wxString tn = t.GetName();
wxString sc = t.GetSchema();
if (!sc.IsEmpty()) dbl = dbl + ((dbl.length() > 0 ? "," : "") + wxString::Format("('%s','%s')", sc, tn));
else onlytab = onlytab + ((onlytab.length() > 0 ? "," : "") + wxString::Format("'%s'", tn));
t.SetAlias(alias[i]);
//this->alias.emplace(curr, );
}
if (!dbl.IsEmpty()) dbl = " (cv.relnamespace::regnamespace::text,cv.relname) in (" + dbl + ")"; else dbl = " false";
if (!onlytab.IsEmpty()) onlytab = " or (cv.relname) in (" + onlytab + ")";
whereexp = dbl + onlytab;
getDatabaseViews();
getDatabaseTables(flag);
MapViewColToRelCol(flag);
wxString rezstr;
if (CHKFLAG(flag, TableColsMap::Flag::SEQUENCE_LIST_TABLE)) {
std::vector<std::map<tab_col_struct, wxString>> all_maps;
std::vector<Table*> all_tables;
for (int i = tables.GetCount()-1; i >=0; i--) {
Table t(tables[i]);
wxString tn = t.GetName();
wxString sc = t.GetSchema();
Table* r = GetTableByName(t.GetSchema(), t.GetName());
if (r != NULL) {
r->SetAlias(alias[i]);
std::map<tab_col_struct, wxString> maplefttables;
BuildMapTableColumnsToSQLexp(r, alias[i], maplefttables);
all_maps.push_back(maplefttables);
all_tables.push_back(r);
if (all_tables.size() > 100) break;
}
}
wxArrayString aar;
wxString s;
bool isadd = false;
bool isexp = !leftexp.IsEmpty();
std::unordered_set<wxString> full;
for (int i = 0; i < all_maps.size(); i++) {
//Table* r = all_tables[i];
for (int j = i + 1; j < all_maps.size(); j++) {
wxString tmp= MapTableToTable(all_maps[i], all_maps[j],flag,leftexp);
if (tmp.length() > 0) {
if (tmp.Find('\t') == -1) {
full.insert(tmp);
if (full.size() > 1 && !isexp) isadd = true;
//if (!s.IsEmpty()) { s += " AND"; isadd = true; }
//s += tmp;
aar.Add(tmp);
}
else {
wxArrayString tmp2 = wxSplit(tmp,'\t');
for (int k = 0; k < tmp2.GetCount();k++) {
aar.Add(tmp2[k]);
}
}
}
}
}
//if (!s.IsEmpty() && isadd) rezstr = s + '\t';
wxString space;
if (CHKFLAG(flag, TableColsMap::Flag::NOT_ADD_FIRST_SPACE)) space = " ";
if (full.size() > 1 && isadd) {
for (auto &s1: full) {
if (!rezstr.IsEmpty()) { rezstr += " AND"+space;}
rezstr += s1;
}
if (rezstr.length() > 0 && aar.GetCount()>0) rezstr += '\t';
}
rezstr = rezstr+wxJoin(aar, '\t');
return rezstr;
}
// build all table.colum for join tables
std::map<tab_col_struct,wxString> maplefttables;
for (int i = tables.GetCount() - 1; i > 0; i--) {
Table t(tables[i]);
wxString tn = t.GetName();
wxString sc = t.GetSchema();
Table* r = GetTableByName(t.GetSchema(), t.GetName());
if (r != NULL) {
r->SetAlias(alias[i]);
BuildMapTableColumnsToSQLexp(r,alias[i], maplefttables);
}
}
Table* r = GetTableByName(right.GetSchema(),right.GetName());
if (r != NULL) {
Table tmp = *r;
std::map<tab_col_struct, wxString> maprighttable;
BuildMapTableColumnsToSQLexp(r, alias[0], maprighttable);
rezstr=MapTableToTable(maplefttables, maprighttable,flag,leftexp);
}
return rezstr;
}
wxString TableColsMap::MapTableToTable(std::map<tab_col_struct, wxString>& maplefttables, std::map<tab_col_struct, wxString>& maprighttable, const TableColsMap::Flag flag, const wxString& leftexp) {
std::map<tab_tab_struct, wxString> sqlvariants;
bool isexp = !leftexp.IsEmpty();
for (auto& e : maprighttable) {
tab_col_struct fnd = e.first;
//find in all FK key
for (auto itr = all_fk_index.find(fnd); itr != all_fk_index.end(); itr++) {
if (itr->first.t != fnd.t || itr->first.column != fnd.column) continue; // find not work
tab_col_struct fk = itr->second;
tab_tab_struct tt = { fnd.t,fk.t };
if (auto it2 = maplefttables.find(fk); it2 != maplefttables.end()) {
wxString sqlcol2 = it2->second;
wxString sqlcol1 = e.second;
wxString ttt;
if (isexp) {
// only equal leftexp
if (sqlcol2 == leftexp) {
ttt = sqlcol1;
}
else if (sqlcol1 == leftexp) {
ttt = sqlcol2;
}
else continue;
} else
ttt = sqlcol2 + " = " + sqlcol1;
if (auto it3 = sqlvariants.find(tt); it3 != sqlvariants.end()) {
if (it3->second.Find(ttt) >= 0 || isexp) {
ttt = it3->second;
}
else
ttt = it3->second + " AND " + ttt;
sqlvariants.erase(tt);
}
sqlvariants.insert({ tt,ttt });
}
}
}
wxArrayString rez;
wxString space;
if (!CHKFLAG(flag, TableColsMap::Flag::NOT_ADD_FIRST_SPACE)) space = " ";
for (auto& e : sqlvariants) {
rez.Add(space + e.second);
}
return wxJoin(rez, '\t');
}
Table* TableColsMap::GetTablebyOID(const wxString oid) {
if (auto it2 = oids.find(oid); it2 != oids.end()) {
return it2->second;
}
return NULL;
}
Table* TableColsMap::GetRelTableForViewCol(const wxString oid, int ncolview, int& outrelcol) {
Table *tt = GetTablebyOID(oid);
if (tt != NULL) {
Table t = *tt;
if (tt->GetType() == 'v') {
wxString toid = t[ncolview - 1].linkOid;
if (toid == '0') return NULL;
int ncol = t[ncolview - 1].linknumcol;
if (ncol < 1) return NULL;
return GetRelTableForViewCol(toid,ncol,outrelcol);
}
else {
outrelcol = ncolview;
return tt;
}
}
return NULL;
}
void TableColsMap::MapViewColToRelCol(const TableColsMap::Flag flag) {
for (auto& e : oids) {
Table* t = e.second;
{
Table tmp = *t;
for (int c = 0; c < t->GetColsCount(); c++) {
int linkcol = tmp[c].linknumcol;
Cols cl = tmp[c];
if (t->GetType() != 'v') {
cl.relTable = t;
cl.relcol = cl.num;
t->SetCol(c, cl);
continue;
}
if (linkcol > 0 && cl.relTable==NULL && cl.linkOid!='0') {
if (cl.relcol == -1) continue;
wxString oid = cl.linkOid;
int outrelcol = -1;
Table* rel = GetRelTableForViewCol(oid,linkcol, outrelcol);
cl.relcol = outrelcol;
if (rel != NULL) {
cl.relTable = rel;
}
t->SetCol(c, cl);
}
}
}
}
}
Table *TableColsMap::GetTableByName(const wxString& sch, const wxString& tname) {
for (auto& e : oids) {
Table* t = e.second;
if (t->GetName() == tname) {
if (!sch.IsEmpty() && t->GetSchema() == sch
|| sch.IsEmpty()) {
return t;
}
}
}
return NULL;
}
void Table::Set(wxString kind, wxString sch, wxString toid, wxString tname) {
schema = sch;
if (tname != name) name = tname;
if (kind.length() > 0) type = kind[0]; else type = 0;
oid = toid;
}
void Table::AddColumn(wxString ncol, wxString colname, wxString oidTable, wxString ncolTable) {
Cols c;
c.num = (int)StrToLong(ncol);
c.linkOid = oidTable;
c.linknumcol = (int)StrToLong(ncolTable);
c.name = colname;
cols.push_back(c);
}
void TableColsMap::getDatabaseViews() {
wxString sql = R"(select cv.oid,cv.relname,cv.relnamespace::regnamespace,cv.relkind, r.ev_action from --pg_class cv
pg_rewrite r
join pg_depend dp on dp.objid=r.oid and dp.deptype='i' and r.rulename='_RETURN' and dp.classid::regclass::text='pg_rewrite'
join pg_class cv on cv.oid=dp.refobjid
where
)";
wxString oidslist="";
wxString sql2;
sql2= sql + whereexp;
while (sql2.length() > 0) {
pgSet* dataSet1 = db->ExecuteSet(sql2);
if (dataSet1)
{
oidslist = "";
Table* t;
while (!dataSet1->Eof())
{
wxString sc = dataSet1->GetVal(wxT("relnamespace"));
wxString tn = dataSet1->GetVal(wxT("relname"));
wxString kind = dataSet1->GetVal(wxT("relkind"));
wxString action = dataSet1->GetVal(wxT("ev_action"));
wxString oid = dataSet1->GetVal(wxT("oid"));
//std::map<Table*, std::vector<LinkTable>>::iterator it;
//for (it = table_links.begin(); it != table_links.end();++it) {
int ncolmax = 0;
if (auto it = oids.find(oid); it != oids.end())
{
t = it->second;
}
else {
t = new Table();
oids.insert({ oid, t });
}
if (t->GetType() == 0)
{
t->Set(kind, sc, oid, tn);
while (regaction.Matches(action))
{
size_t start, len;
regaction.GetMatch(&start, &len, 0);
if (len == 0) break;
wxString ncol = regaction.GetMatch(action, 3);
int tmp = (int)StrToLong(ncol);
wxString colname = regaction.GetMatch(action, 4);
wxString oidTable = regaction.GetMatch(action, 5);
wxString ncolTable = regaction.GetMatch(action, 6);
if (tmp <= ncolmax) break; // ignore columns
ncolmax = tmp;
t->AddColumn(ncol, colname, oidTable, ncolTable);
action = action.Mid(start + len);
if (oidTable != "0" && oids.find(oidTable) == oids.end()) {
wxString tmpname;
tmpname = "";
Table* t2 = new Table();
t2->Set("", "", oidTable, tmpname);
oids.insert({ oidTable, t2 });
//cuurent_list.push_back(t2);
//oids.insert({ oid, t });
if (!oidslist.IsEmpty()) oidslist += ',';
oidslist += wxString::Format("'%s'", oidTable);
}
}
}
dataSet1->MoveNext();
}
delete dataSet1;
//
if (oidslist.IsEmpty()) sql2 = ""; else {
sql2 = sql + " cv.oid in (" + oidslist + ")";
}
}
else break;
}
}
void TableColsMap::getDatabaseTables(const TableColsMap::Flag flag) {
//std::vector<Table*> cuurent_list;
wxString onlyoid;
for (auto& e : oids) {
Table* t = e.second;
if (t->GetType() == 0 && !t->GetOID().IsEmpty()) {
wxString oid = t->GetOID();
onlyoid = onlyoid + ((onlyoid.length() > 0 ? "," : "") + wxString::Format("'%s'", oid));
}
}
wxString sql = R"(
select cv.oid,cv.relname,cv.relnamespace::regnamespace, cv.relkind, a.attnum,a.attname from pg_class cv,pg_attribute a
where cv.oid=a.attrelid and a.attnum>0 and a.attisdropped=false and ()";
if (!onlyoid.IsEmpty()) sql = sql + "cv.oid in(" + onlyoid + ") "; else sql = sql + "false";
if (!whereexp.IsEmpty()) {
sql = sql + " or (" + whereexp + " )";
}
sql+= +") order by cv.oid,attnum";
pgSet* dataSet1 = db->ExecuteSet(sql);
if (dataSet1)
{
Table* t;
wxString prevoid;
while (!dataSet1->Eof())
{
wxString oid = dataSet1->GetVal(wxT("oid"));
wxString sc = dataSet1->GetVal(wxT("relnamespace"));
wxString tn = dataSet1->GetVal(wxT("relname"));
wxString kind = dataSet1->GetVal(wxT("relkind"));
wxString ncol= dataSet1->GetVal(wxT("attnum"));
int num= (int)StrToLong(ncol);
wxString colname = dataSet1->GetVal(wxT("attname"));
if (kind != "v") {
if (prevoid != oid) {
prevoid = oid;
if (auto it = oids.find(oid); it != oids.end()) {
t = it->second;
t->Set(kind, sc, oid, tn);
}
else {
// first table
onlyoid = onlyoid + ((onlyoid.length() > 0 ? "," : "") + wxString::Format("'%s'", oid));
t = new Table();
t->Set(kind, sc, oid, tn);
oids.insert({ oid, t });
}
}
t->AddColumn(ncol, colname, "", "");
}
dataSet1->MoveNext();
}
delete dataSet1;
}
// FK info
wxString sqlfk = R"(
select rel.oid
,( select string_agg(attnum::text, ',' order by ordinality)
from pg_attribute,
unnest(c.conkey) with ordinality
where attrelid = c.conrelid
and attnum = unnest
) con_col_list
,relf.oid foid
,( select string_agg(attnum::text, ',' order by ordinality)
from pg_attribute,
unnest(c.confkey) with ordinality
where attrelid = c.confrelid
and attnum = unnest
) conf_col_list
from pg_constraint c
left join pg_class rel on rel.oid=c.conrelid
left join pg_class relf on relf.oid=c.confrelid
where c.contype in ('f','p')
and rel.oid in ()" + onlyoid+") order by oid,foid";
if (onlyoid.IsEmpty()) return;
dataSet1 = db->ExecuteSet(sqlfk);
std::set<fk_full_struct> uniq_fk_index;
if (dataSet1)
{
Table* t=NULL;
std::vector<LinkTableFK> all_fk_table;
std::map<wxString, tab_col_struct> forein_unknown_tab; //[foid+fcol]=childTable
while (!dataSet1->Eof())
{
wxString oid = dataSet1->GetVal(wxT("oid"));
wxString foid = dataSet1->GetVal(wxT("foid"));
wxString collist = dataSet1->GetVal(wxT("con_col_list"));
wxString colflist = dataSet1->GetVal(wxT("conf_col_list"));
if (auto it = oids.find(oid); it != oids.end()) {
if (t != it->second) {
//if (all_fk_table.size()>0) tablechild_fk[tf] = fk;
}
t = it->second;
wxArrayString ar = wxSplit(collist, ',');
wxArrayString arf = wxSplit(colflist, ',');
Table tmp = *t;
//tablechild_fk
LinkTableFK fk;
fk.parent = t;
Table* tf = NULL;
fk.child = NULL;
for (int k = 0; k < ar.GetCount(); k++) {
int i = (int)StrToLong(ar[k]);
fk.colsp.push_back(i);
if (foid.IsEmpty()) {
tmp[i - 1].pk = true;
continue;
}
int fi = (int)StrToLong(arf[k]);
fk.colsc.push_back(fi);
tmp[i-1].linknumcol = fi;
tmp[i-1].linkOid = foid;
if (auto it2 = oids.find(foid); it2 != oids.end()) {
tf = it2->second;
tmp[i - 1].linkTable = tf;
if (fk.child == NULL) fk.child = tf;
}
else {
wxString key = foid +','+ arf[k];
if (auto it2 = forein_unknown_tab.find(key); it2 != forein_unknown_tab.end()) {
// Óæå áûëà ýòà òàáëèöà è êîëîíêà. Ñâÿæåì äâå íàøè òàáëèöû ÷åðåç òðåòüþ.
// ìîæíî ñâÿçàòü íàøè òàáëèöû ÷åðåç íåèçâåñòíóþ .Óâû òîëüêî îäèí óðîâåíü âëîæåííîñòè.
tab_col_struct parent= it2->second;
tf = parent.t;
tmp[i - 1].linkTable = tf;
fi = parent.column;
if (fk.child == NULL) fk.child = tf;
}
else {
// ññûëêà íà íå èçâåñòíóþ òàáëèöó çàïîìíèì å¸ è Íàøó òàáëèöó
tab_col_struct parent = { t,i };
forein_unknown_tab.insert({ key,parent });
}
}
if (fk.child != NULL) {
tab_col_struct parent = { t,i }, child = {tf,fi};
all_fk_index.insert({ parent,child });
all_fk_index.insert({ child,parent }); // ó÷òåì êîìóòàòèâíîñòü a=b , òî b=a
// uniq index
uniq_fk_index.insert({ parent,child });
uniq_fk_index.insert({ child,parent });
}
}
if (tf != NULL) {
//all_fk_table=tablechild_fk[tf];
//all_fk_table.push_back(fk);
//tablechild_fk[tf] = all_fk_table;
}
//t->Set(kind, sc, oid, tn);
}
dataSet1->MoveNext();
}
delete dataSet1;
}
if (CHKFLAG(flag, TableColsMap::Flag::USE_TRANSIT_FK)) {
// äîáàâèì òðàíçèòèâíîñòè a=b b=c , òî a=c
std::set<fk_full_struct> transit;
int lvl = 0;
for (auto e: all_fk_index) {
tab_col_struct fnd=e.second;
search_link(e.first, e.second,transit,lvl);
}
for (auto &e : transit) {
//tab_col_struct fnd = e.second;
if (uniq_fk_index.find(e)== uniq_fk_index.end()) {
all_fk_index.insert({e.left,e.right});
}
}
}
}
tab_col_struct TableColsMap::search_link(tab_col_struct start, tab_col_struct chknode, std::set<fk_full_struct> &traz, int level) {
//tab_col_struct fnd = e.second;
if (start.t == chknode.t || level>10) return { NULL,1 };
fk_full_struct full_search = { start,chknode };
for (auto itr = all_fk_index.find(chknode); itr != all_fk_index.end(); itr++) {
if (itr->first.t != chknode.t || itr->first.column != chknode.column) {
//wxTrap();
continue; // find not work
}
if (start == itr->second) // back reference ignore
continue;
if (itr->second.t == chknode.t) continue;
fk_full_struct unik = { start,itr->second };
auto [iter, has_been_inserted]=traz.insert(unik);
//tab_col_struct fk = itr->second;
if (has_been_inserted) search_link(start, itr->second, traz, level + 1);
}
return { NULL,1 };
};

View file

@ -178,7 +178,7 @@ static const SchemaQuery Query_for_list_of_tsv = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
"c.relkind IN ('r', 'S', 'v')",
"c.relkind IN ('r', 'S', 'v','m','f','p')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@ -1628,6 +1628,9 @@ static char * psql_completion(char *text, int start, int end, void *dbptr)
pg_strcasecmp(prev3_wd, "COPY") != 0 &&
pg_strcasecmp(prev3_wd, "\\copy") != 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
/* ... JOIN ... */
else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
/* Backslash commands */