diff --git a/ctl/ctlListView.cpp b/ctl/ctlListView.cpp index 4f3b76d..6909922 100644 --- a/ctl/ctlListView.cpp +++ b/ctl/ctlListView.cpp @@ -19,13 +19,106 @@ #include "ctl/ctlListView.h" #include "utils/misc.h" - -ctlListView::ctlListView(wxWindow *p, int id, wxPoint pos, wxSize siz, long attr) - : wxListView(p, id, pos, siz, attr | wxLC_REPORT) +int wxCALLBACK +MyCompareFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr WXUNUSED(sortData)) { + // inverse the order + if (item1 < item2) + return 1; + if (item1 > item2) + return -1; + + return 0; } +ctlListView::ctlListView(wxWindow* p, int id, wxPoint pos, wxSize siz, long attr) + : wxListView(p, id, pos, siz, attr | wxLC_REPORT) +{ + nosort = false; + order = 1; + prev_col = -1; + Connect(wxID_ANY, wxEVT_LIST_COL_CLICK, wxListEventHandler(ctlListView::OnSortGrid)); +} +#include +void ctlListView::OnSortGrid(wxListEvent& event) +{ + if (!nosort) { + int col = event.GetColumn(); + if (col == prev_col) order = order * -1; + int rows = GetItemCount(); + wxListItem listitem; + listitem.SetMask(wxLIST_MASK_TEXT); + GetColumn(col, listitem); + wxString label = listitem.GetText(); + bool issize = label == _("Size"); + bool astext = true; + if (label == _("CFS fragmentation") || + label == (_("Tuples inserted")) || + label == (_("Tuples updated")) || + label == (_("Tuples deleted")) || + label == (_("Tuples HOT updated")) || + label == (_("Live tuples")) || + label == (_("Dead tuples")) || + label == (_("CFS %")) || + label == (_("Autovacuum counter")) || + label == (_("Autoanalyze counter")) || + label == (_("Index Scans")) || + label == (_("Index Tuples Read")) || + label == (_("Index Tuples Fetched")) || + issize + ) { + astext = false; + } + //sort numeric column + if (astext) { + std::multimap mp; + for (int i = 0; i < rows; i++) { + wxString val = GetItemText(i, col); + mp.insert(std::pair(val, i)); + } + // сопоставим сортированным значениям последовательность чисел для сортировки SortItems + std::multimap::iterator it = mp.begin(); + for (int i = 1; it != mp.end(); it++, i++) { // выводим их + int row = it->second; // row + wxListView::SetItemData(row, (long)i * order); + } + } + else + { + std::multimap mp; + double d; + for (int i = 0; i < rows; i++) { + wxString val = GetItemText(i, col); + if (val == "NaN") val = "0"; + if (val.ToCDouble(&d)) + { + // это число + } + else + { + + if (issize) + if (val.Right(2) == "kB") d = d / 1024; + else if (val.Right(2) == "GB") d = d * 1024; + else if (val.Right(5) == "bytes") d = d / 1024 / 1024; + } + mp.insert(std::pair(d, i)); + } + // сопоставим сортированным значениям последовательность чисел для сортировки SortItems + std::multimap::iterator it = mp.begin(); + for (int i = 1; it != mp.end(); it++, i++) { // выводим их + int row = it->second; // row + wxListView::SetItemData(row, (long)i * order); + } + + } + + SortItems(MyCompareFunction, 0); + Refresh(); + prev_col = col; + } +} long ctlListView::GetSelection() { return GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); @@ -43,7 +136,7 @@ wxString ctlListView::GetText(long row, long col) }; -void ctlListView::AddColumn(const wxString &text, int size, int format) +void ctlListView::AddColumn(const wxString& text, int size, int format) { if (size == wxLIST_AUTOSIZE || size == wxLIST_AUTOSIZE_USEHEADER) { @@ -56,7 +149,7 @@ void ctlListView::AddColumn(const wxString &text, int size, int format) } -long ctlListView::AppendItem(int icon, const wxString &val, const wxString &val2, const wxString &val3, const wxString &val4) +long ctlListView::AppendItem(int icon, const wxString& val, const wxString& val2, const wxString& val3, const wxString& val4) { long pos = InsertItem(GetItemCount(), val, icon); if (!val2.IsEmpty()) @@ -70,7 +163,7 @@ long ctlListView::AppendItem(int icon, const wxString &val, const wxString &val2 } -void ctlListView::CreateColumns(wxImageList *images, const wxString &left, const wxString &right, int leftSize) +void ctlListView::CreateColumns(wxImageList* images, const wxString& left, const wxString& right, int leftSize) { int rightSize; if (leftSize < 0) @@ -107,7 +200,7 @@ void ctlListView::CreateColumns(wxImageList *images, const wxString &left, const } -void ctlListView::CreateColumns(wxImageList *images, const wxString &str1, const wxString &str2, const wxString &str3, int leftSize) +void ctlListView::CreateColumns(wxImageList* images, const wxString& str1, const wxString& str2, const wxString& str3, int leftSize) { int rightSize; if (leftSize < 0) diff --git a/include/ctl/ctlListView.h b/include/ctl/ctlListView.h index d2a9e4e..db2736f 100644 --- a/include/ctl/ctlListView.h +++ b/include/ctl/ctlListView.h @@ -21,46 +21,53 @@ class frmMain; class ctlListView : public wxListView { +private: + void OnSortGrid(wxListEvent& event); + bool nosort; // если кто то пользуется SetItemData то не будем сортировать такие ctlListView + int order, prev_col; public: - ctlListView(wxWindow *p, int id, wxPoint pos, wxSize siz, long attr = 0); + bool SetItemData(long item, long data) { + nosort = true; + return wxListView::SetItemData(item, data); + } + ctlListView(wxWindow* p, int id, wxPoint pos, wxSize siz, long attr = 0); long GetSelection(); wxString GetText(long row, long col = 0); + void CreateColumns(wxImageList* images, const wxString& left, const wxString& right, int leftSize = 60); + void CreateColumns(wxImageList* images, const wxString& str1, const wxString& str2, const wxString& str3, int leftSize = 60); - void CreateColumns(wxImageList *images, const wxString &left, const wxString &right, int leftSize = 60); - void CreateColumns(wxImageList *images, const wxString &str1, const wxString &str2, const wxString &str3, int leftSize = 60); + void AddColumn(const wxString& text, int size = wxLIST_AUTOSIZE_USEHEADER, int format = wxLIST_FORMAT_LEFT); - void AddColumn(const wxString &text, int size = wxLIST_AUTOSIZE_USEHEADER, int format = wxLIST_FORMAT_LEFT); - - long AppendItem(int icon, const wxString &val, const wxString &val2 = wxString(), const wxString &val3 = wxString(), const wxString &val4 = wxString()); - long AppendItem(const wxString &val, const wxString &val2 = wxString(), const wxString &val3 = wxString()) + long AppendItem(int icon, const wxString& val, const wxString& val2 = wxString(), const wxString& val3 = wxString(), const wxString& val4 = wxString()); + long AppendItem(const wxString& val, const wxString& val2 = wxString(), const wxString& val3 = wxString()) { return AppendItem(PGICON_PROPERTY, val, val2, val3); } - void AppendItem(const wxString &str, long l) + void AppendItem(const wxString& str, long l) { AppendItem(str, NumToStr(l)); } - void AppendItem(const wxString &str, double d) + void AppendItem(const wxString& str, double d) { AppendItem(str, NumToStr(d)); } - void AppendItem(const wxString &str, OID o) + void AppendItem(const wxString& str, OID o) { AppendItem(str, NumToStr(o)); } - void AppendItem(const wxString &str, const wxDateTime &d) + void AppendItem(const wxString& str, const wxDateTime& d) { AppendItem(str, DateToStr(d)); } - void AppendItem(const wxString &str, const wxLongLong &l) + void AppendItem(const wxString& str, const wxLongLong& l) { AppendItem(str, l.ToString()); } - void AppendItem(const wxString &str, const wxULongLong &l) + void AppendItem(const wxString& str, const wxULongLong& l) { AppendItem(str, l.ToString()); } - void AppendYesNoItem(const wxString &str, bool b) + void AppendYesNoItem(const wxString& str, bool b) { AppendItem(str, BoolToYesNo(b)); } @@ -69,6 +76,7 @@ public: { DeleteItem(GetSelection()); } + }; diff --git a/include/schema/pgObject.h b/include/schema/pgObject.h index da7cd24..d18af0f 100644 --- a/include/schema/pgObject.h +++ b/include/schema/pgObject.h @@ -269,6 +269,7 @@ public: virtual void ShowTreeDetail(ctlTree *browser, frmMain *form = 0, ctlListView *properties = 0, ctlSQLBox *sqlPane = 0) = 0; virtual void ShowStatistics(frmMain *form, ctlListView *statistics); + virtual void ShowStatisticsTables(frmMain* form, ctlListView* statistics, pgObject* obj); virtual void ShowDependencies(frmMain *form, ctlListView *Dependencies, const wxString &where = wxEmptyString); virtual void ShowDependents(frmMain *form, ctlListView *referencedBy, const wxString &where = wxEmptyString); virtual pgObject *Refresh(ctlTree *browser, const wxTreeItemId item) diff --git a/schema/pgIndex.cpp b/schema/pgIndex.cpp index 1e4ff18..b40ba7c 100644 --- a/schema/pgIndex.cpp +++ b/schema/pgIndex.cpp @@ -787,4 +787,5 @@ void pgIndexBaseCollection::ShowStatistics(frmMain *form, ctlListView *statistic delete stats; } + statistics->SetColumnWidth(0, wxLIST_AUTOSIZE); } diff --git a/schema/pgObject.cpp b/schema/pgObject.cpp index b586a63..c4a2fb0 100644 --- a/schema/pgObject.cpp +++ b/schema/pgObject.cpp @@ -67,6 +67,8 @@ #include "agent/pgaSchedule.h" #include "agent/pgaStep.h" #include "schema/pgPartition.h" +#include "utils/pgfeatures.h" + int pgObject::GetType() const { @@ -224,6 +226,120 @@ void pgObject::ShowStatistics(frmMain *form, ctlListView *statistics) { } +void pgObject::ShowStatisticsTables(frmMain* form, ctlListView* statistics, pgObject *obj) +{ + wxLogInfo(wxT("Displaying statistics for tables on %s"), obj->GetSchema()->GetIdentifier().c_str()); + bool tabcoll = false; + bool partcoll = false; + bool onetable = false; + if (IsCollection()) { + wxString t = obj->GetFactory()->GetTypeName(); + if (t == ("Tables") + //obj->IsCreatedBy(tableFactory) + ) tabcoll = true; + if ( t == ("Partitions")) partcoll = true; + } + else onetable = true; + bool hasSize = obj->GetConnection()->HasFeature(FEATURE_SIZE); + + // Add the statistics view columns + statistics->ClearAll(); + statistics->AddColumn(_("Table Name")); + if (hasSize) + statistics->AddColumn(_("Size")); + if (obj->GetConnection()->GetIsPgProEnt()) statistics->AddColumn(_("CFS %")); + statistics->AddColumn(_("Tuples inserted")); + statistics->AddColumn(_("Tuples updated")); + statistics->AddColumn(_("Tuples deleted")); + if (obj->GetConnection()->BackendMinimumVersion(8, 3)) + { + statistics->AddColumn(_("Tuples HOT updated")); + statistics->AddColumn(_("Live tuples")); + statistics->AddColumn(_("Dead tuples")); + } + if (obj->GetConnection()->BackendMinimumVersion(8, 2)) + { + statistics->AddColumn(_("Last vacuum")); + statistics->AddColumn(_("Last autovacuum")); + statistics->AddColumn(_("Last analyze")); + statistics->AddColumn(_("Last autoanalyze")); + } + if (obj->GetConnection()->BackendMinimumVersion(9, 1)) + { + statistics->AddColumn(_("Vacuum counter")); + statistics->AddColumn(_("Autovacuum counter")); + statistics->AddColumn(_("Analyze counter")); + statistics->AddColumn(_("Autoanalyze counter")); + } + + wxString sql = wxT("SELECT st.relname, n_tup_ins, n_tup_upd, n_tup_del"); + if (obj->GetConnection()->BackendMinimumVersion(8, 3)) + sql += wxT(", n_tup_hot_upd, n_live_tup, n_dead_tup"); + if (obj->GetConnection()->BackendMinimumVersion(8, 2)) + sql += wxT(", last_vacuum, last_autovacuum, last_analyze, last_autoanalyze"); + if (obj->GetConnection()->BackendMinimumVersion(9, 1)) + sql += wxT(", vacuum_count, autovacuum_count, analyze_count, autoanalyze_count"); + if (hasSize) + sql += wxT(", pg_size_pretty(pg_relation_size(st.relid)") + wxT(" + CASE WHEN cl.reltoastrelid = 0 THEN 0 ELSE pg_relation_size(cl.reltoastrelid) + COALESCE((SELECT SUM(pg_relation_size(indexrelid)) FROM pg_index WHERE indrelid=cl.reltoastrelid)::int8, 0) END") + wxT(" + COALESCE((SELECT SUM(pg_relation_size(indexrelid)) FROM pg_index WHERE indrelid=st.relid)::int8, 0)) AS size"); + if (obj->GetConnection()->GetIsPgProEnt()) sql += wxT(",left((cfs_fragmentation(cl.oid)*100)::text,5)::text AS cfs_ratio"); + sql += wxT("\n FROM pg_stat_all_tables st") + wxT(" JOIN pg_class cl on cl.oid=st.relid\n"); + if (partcoll) sql += wxT(" JOIN pg_inherits i ON (cl.oid = i.inhrelid) WHERE "); + if (tabcoll) sql += wxT(" WHERE schemaname = ")+obj->qtDbString(obj->GetSchema()->GetName()); + if (partcoll) sql += wxT(" i.inhparent = ") + obj->GetOidStr(); + //+ obj->qtDbString(obj->GetSchema()->GetName()) + wxT(" AND i.inhparent = ") + obj->GetOidStr() + if (onetable) sql += wxT("join (select t.relid::oid oid from pg_partition_tree(")+ (obj->GetOidStr())+wxT("::regclass) t where t.isleaf = 't') t on t.oid=cl.oid"); + + sql += wxT("\n ORDER BY relname"); + + pgSet* stats = obj->GetDatabase()->ExecuteSet(sql); + + if (stats) + { + long pos = 0; + int i; + while (!stats->Eof()) + { + i = 1; + statistics->InsertItem(pos, stats->GetVal(wxT("relname")), PGICON_STATISTICS); + if (hasSize) + statistics->SetItem(pos, i++, stats->GetVal(wxT("size"))); + if (obj->GetConnection()->GetIsPgProEnt()) statistics->SetItem(pos, i++, stats->GetVal(wxT("cfs_ratio"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_ins"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_upd"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_del"))); + if (obj->GetConnection()->BackendMinimumVersion(8, 3)) + { + statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_hot_upd"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("n_live_tup"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("n_dead_tup"))); + } + if (obj->GetConnection()->BackendMinimumVersion(8, 2)) + { + statistics->SetItem(pos, i++, stats->GetVal(wxT("last_vacuum"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("last_autovacuum"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("last_analyze"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("last_autoanalyze"))); + } + if (obj->GetConnection()->BackendMinimumVersion(9, 1)) + { + statistics->SetItem(pos, i++, stats->GetVal(wxT("vacuum_count"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("autovacuum_count"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("analyze_count"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("autoanalyze_count"))); + } + stats->MoveNext(); + pos++; + } + + delete stats; + } + statistics->SetColumnWidth(0, wxLIST_AUTOSIZE); + +} + bool pgObject::UpdateIcon(ctlTree *browser) { diff --git a/schema/pgPartition.cpp b/schema/pgPartition.cpp index 3a6d881..0b1a058 100644 --- a/schema/pgPartition.cpp +++ b/schema/pgPartition.cpp @@ -97,6 +97,8 @@ pgPartitionCollection::pgPartitionCollection(pgaFactory *factory, pgPartition *_ } void pgPartitionCollection::ShowStatistics(frmMain *form, ctlListView *statistics) { + ShowStatisticsTables(form, statistics, this); + return; wxLogInfo(wxT("Displaying statistics for tables on %s"), GetSchema()->GetIdentifier().c_str()); bool hasSize = GetConnection()->HasFeature(FEATURE_SIZE); @@ -104,6 +106,9 @@ void pgPartitionCollection::ShowStatistics(frmMain *form, ctlListView *statistic // Add the statistics view columns statistics->ClearAll(); statistics->AddColumn(_("Table Name")); + if (hasSize) + statistics->AddColumn(_("Size")); + if (GetConnection()->GetIsPgProEnt()) statistics->AddColumn(_("CFS %")); statistics->AddColumn(_("Tuples inserted")); statistics->AddColumn(_("Tuples updated")); statistics->AddColumn(_("Tuples deleted")); @@ -127,8 +132,6 @@ void pgPartitionCollection::ShowStatistics(frmMain *form, ctlListView *statistic statistics->AddColumn(_("Analyze counter")); statistics->AddColumn(_("Autoanalyze counter")); } - if (hasSize) - statistics->AddColumn(_("Size"), 50); wxString sql = wxT("SELECT st.relname, n_tup_ins, n_tup_upd, n_tup_del"); if (GetConnection()->BackendMinimumVersion(8, 3)) @@ -141,7 +144,7 @@ void pgPartitionCollection::ShowStatistics(frmMain *form, ctlListView *statistic sql += wxT(", pg_size_pretty(pg_relation_size(st.relid)") wxT(" + CASE WHEN cl.reltoastrelid = 0 THEN 0 ELSE pg_relation_size(cl.reltoastrelid) + COALESCE((SELECT SUM(pg_relation_size(indexrelid)) FROM pg_index WHERE indrelid=cl.reltoastrelid)::int8, 0) END") wxT(" + COALESCE((SELECT SUM(pg_relation_size(indexrelid)) FROM pg_index WHERE indrelid=st.relid)::int8, 0)) AS size"); - + if (GetConnection()->GetIsPgProEnt()) sql += wxT(",left((cfs_fragmentation(cl.oid)*100)::text,5)::text AS cfs_ratio"); sql += wxT("\n FROM pg_stat_all_tables st") wxT(" JOIN pg_class cl on cl.oid=st.relid\n") wxT(" JOIN pg_inherits i ON (cl.oid = i.inhrelid)") @@ -156,11 +159,14 @@ void pgPartitionCollection::ShowStatistics(frmMain *form, ctlListView *statistic int i; while (!stats->Eof()) { - i = 4; + i = 1; statistics->InsertItem(pos, stats->GetVal(wxT("relname")), PGICON_STATISTICS); - statistics->SetItem(pos, 1, stats->GetVal(wxT("n_tup_ins"))); - statistics->SetItem(pos, 2, stats->GetVal(wxT("n_tup_upd"))); - statistics->SetItem(pos, 3, stats->GetVal(wxT("n_tup_del"))); + if (hasSize) + statistics->SetItem(pos, i++, stats->GetVal(wxT("size"))); + if (GetConnection()->GetIsPgProEnt()) statistics->SetItem(pos, i++, stats->GetVal(wxT("cfs_ratio"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_ins"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_upd"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_del"))); if (GetConnection()->BackendMinimumVersion(8, 3)) { statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_hot_upd"))); @@ -181,14 +187,13 @@ void pgPartitionCollection::ShowStatistics(frmMain *form, ctlListView *statistic statistics->SetItem(pos, i++, stats->GetVal(wxT("analyze_count"))); statistics->SetItem(pos, i++, stats->GetVal(wxT("autoanalyze_count"))); } - if (hasSize) - statistics->SetItem(pos, i, stats->GetVal(wxT("size"))); stats->MoveNext(); pos++; } delete stats; } + statistics->SetColumnWidth(0, wxLIST_AUTOSIZE); } diff --git a/schema/pgTable.cpp b/schema/pgTable.cpp index 7ebbcc4..aead2af 100644 --- a/schema/pgTable.cpp +++ b/schema/pgTable.cpp @@ -1290,6 +1290,8 @@ wxString pgTableCollection::GetTranslatedMessage(int kindOfMessage) const void pgTableCollection::ShowStatistics(frmMain *form, ctlListView *statistics) { + ShowStatisticsTables(form, statistics, this); + return; wxLogInfo(wxT("Displaying statistics for tables on %s"), GetSchema()->GetIdentifier().c_str()); bool hasSize = GetConnection()->HasFeature(FEATURE_SIZE); @@ -1299,6 +1301,8 @@ void pgTableCollection::ShowStatistics(frmMain *form, ctlListView *statistics) statistics->AddColumn(_("Table Name")); if (hasSize) statistics->AddColumn(_("Size"), 50); + if (GetConnection()->GetIsPgProEnt()) statistics->AddColumn(_("CFS %")); + statistics->AddColumn(_("Tuples inserted")); statistics->AddColumn(_("Tuples updated")); @@ -1335,6 +1339,7 @@ void pgTableCollection::ShowStatistics(frmMain *form, ctlListView *statistics) sql += wxT(", pg_size_pretty(pg_relation_size(st.relid)") wxT(" + CASE WHEN cl.reltoastrelid = 0 THEN 0 ELSE pg_relation_size(cl.reltoastrelid) + COALESCE((SELECT SUM(pg_relation_size(indexrelid)) FROM pg_index WHERE indrelid=cl.reltoastrelid)::int8, 0) END") wxT(" + COALESCE((SELECT SUM(pg_relation_size(indexrelid)) FROM pg_index WHERE indrelid=st.relid)::int8, 0)) AS size"); + if (GetConnection()->GetIsPgProEnt()) sql += wxT(",left((cfs_fragmentation(cl.oid)*100)::text,5)::text AS cfs_ratio"); sql += wxT("\n FROM pg_stat_all_tables st") wxT(" JOIN pg_class cl on cl.oid=st.relid\n") @@ -1349,13 +1354,14 @@ void pgTableCollection::ShowStatistics(frmMain *form, ctlListView *statistics) int i; while (!stats->Eof()) { - i = 5; + i = 1; statistics->InsertItem(pos, stats->GetVal(wxT("relname")), PGICON_STATISTICS); if (hasSize) - statistics->SetItem(pos, 1, stats->GetVal(wxT("size"))); - statistics->SetItem(pos, 2, stats->GetVal(wxT("n_tup_ins"))); - statistics->SetItem(pos, 3, stats->GetVal(wxT("n_tup_upd"))); - statistics->SetItem(pos, 4, stats->GetVal(wxT("n_tup_del"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("size"))); + if (GetConnection()->GetIsPgProEnt()) statistics->SetItem(pos, i++, stats->GetVal(wxT("cfs_ratio"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_ins"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_upd"))); + statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_del"))); if (GetConnection()->BackendMinimumVersion(8, 3)) { statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_hot_upd"))); @@ -1391,6 +1397,10 @@ void pgTableCollection::ShowStatistics(frmMain *form, ctlListView *statistics) void pgTable::ShowStatistics(frmMain *form, ctlListView *statistics) { + if (GetIsPartitioned()) { + ShowStatisticsTables(form,statistics,this); + return; + } wxString sql = wxT("SELECT seq_scan AS ") + qtIdent(_("Sequential Scans")) + wxT(", seq_tup_read AS ") + qtIdent(_("Sequential Tuples Read")) +