#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 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 &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> all_maps; std::vector 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 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 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 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 maprighttable; BuildMapTableColumnsToSQLexp(r, alias[0], maprighttable); rezstr=MapTableToTable(maplefttables, maprighttable,flag,leftexp); } return rezstr; } wxString TableColsMap::MapTableToTable(std::map& maplefttables, std::map& maprighttable, const TableColsMap::Flag flag, const wxString& leftexp) { std::map 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>::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 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 uniq_fk_index; if (dataSet1) { Table* t=NULL; std::vector all_fk_table; std::map 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 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 &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 }; };