#include "TablesTableModel.h" #include "OpenDatabase.h" #include "ScopeGuard.h" #include "catalog/PgDatabaseCatalog.h" #include "catalog/PgClass.h" #include "catalog/PgClassContainer.h" #include "catalog/PgNamespace.h" #include "catalog/PgInheritsContainer.h" //#include "Pgsql_declare.h" #include "ui/catalog/tables/TableTreeBuilder.h" #include "CustomDataRole.h" #include #include #include "Pgsql_Connection.h" TablesTableModel::TablesTableModel(std::shared_ptr opendatabase, QObject *parent) : QAbstractItemModel(parent) , openDatabase(opendatabase) {} void TablesTableModel::setNamespaceFilter(NamespaceFilter nsf) { m_namespaceFilter = nsf; refresh(); } void TablesTableModel::setCatalog(std::shared_ptr cat) { if (cat != m_catalog) { m_catalog = cat; refreshConnection = connect(m_catalog.get(), &PgDatabaseCatalog::refreshed, this, &TablesTableModel::refresh); } refresh(); } bool TableLike(RelKind relkind) { switch (relkind) { case RelKind::Table: case RelKind::View: case RelKind::MaterializedView: case RelKind::ForeignTable: case RelKind::PartitionedTable: return true; default: return false; } } void TablesTableModel::refresh() { beginResetModel(); SCOPE_EXIT { endResetModel(); }; if (!m_catalog) return; auto classes = m_catalog->classes(); rootNode.reset(); auto nsfilter = GetNamespaceFilterLambda(); std::map temp; for (const auto &e : *classes) if (TableLike(e.kind) && nsfilter(e)) temp.emplace(e.oid(), e); TableTreeBuilder treeBuilder(temp, *m_catalog->inherits()); auto [rn, oidIndex] = treeBuilder.Build(); rootNode = rn; StartLoadTableSizes(std::move(oidIndex)); } QVariant TablesTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole) { switch (section) { case NameCol: return tr("Name"); case NamespaceCol: return tr("Schema"); case KindCol: return tr("Kind"); case OwnerCol: return tr("Owner"); case TablespaceCol: return tr("Tablespace"); case OptionsCol: return tr("Options"); case AclCol: return tr("ACL"); case CommentCol: return tr("Comment"); case TotalSizeCol: return tr("Total size"); case TableSizeCol: return tr("Table size"); case IndexSizeCol: return tr("Index size"); case ToastSizeCol: return tr("TOAST size"); } } } return QVariant(); } int TablesTableModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { const TableNode *parentItem = static_cast(parent.internalPointer()); return static_cast(parentItem->children.size()); } if (rootNode != nullptr) return static_cast(rootNode->children.size()); return 0; } int TablesTableModel::columnCount(const QModelIndex &) const { return colCount; } QModelIndex TablesTableModel::index(int row, int column, const QModelIndex &parent) const { if (parent.isValid()) { const TableNode *parentItem = static_cast(parent.internalPointer()); return createIndex(row, column, parentItem->getChildPtr(row)); } return createIndex(row, column, rootNode->getChildPtr(row)); } QModelIndex TablesTableModel::parent(const QModelIndex &index) const { if (index.isValid()) { const TableNode *item = static_cast(index.internalPointer()); auto parent = item->parent.lock(); if (parent && parent != rootNode) { return createIndex(parent->myIndex, 0, parent.get()); } } return {}; } Oid TablesTableModel::getType(int column) const { Oid oid; switch (column) { case TotalSizeCol: case TableSizeCol: case IndexSizeCol: case ToastSizeCol: oid = Pgsql::int8_oid; break; case TablespaceCol: case OwnerCol: case NameCol: case NamespaceCol: case KindCol: case OptionsCol: case AclCol: case CommentCol: default: oid = Pgsql::varchar_oid; } return oid; } QVariant TablesTableModel::getData(const QModelIndex &index) const { // const auto &table = rootNode->children[index.row()]; const TableNode* table = nodeFromIndex(index); const auto &t = table->_class; const auto &s = table->sizes; switch (index.column()) { case NameCol: return t.objectName(); case NamespaceCol: return t.nsName(); case KindCol: return t.typeName(); case OwnerCol: return t.ownerName(); case TablespaceCol: return getTablespaceDisplayString(*m_catalog, t.tablespace); case OptionsCol: break; case AclCol: return t.aclString(); case CommentCol: return t.description; case TotalSizeCol: return s.oid == 0 ? QVariant() : s.totalBytes; case TableSizeCol: return s.oid == 0 ? QVariant() : s.totalBytes - s.indexBytes - s.toastBytes; case IndexSizeCol: return s.oid == 0 ? QVariant() : s.indexBytes; case ToastSizeCol: return s.oid == 0 ? QVariant() : s.toastBytes; } return QVariant(); } QVariant TablesTableModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) return getData(index); else if (role == CustomDataTypeRole) return getType(index.column()); else if (role == CustomDataMeaningRole) { switch (index.column()) { case TotalSizeCol: case TableSizeCol: case IndexSizeCol: case ToastSizeCol: return static_cast(DataMeaning::Bytes); default: return static_cast(DataMeaning::Normal); } } return QVariant(); } void TablesTableModel::StartLoadTableSizes(OidClassIndex oidIndex) { QPointer p(this); QtConcurrent::run([this] { return QueryTableSizes(); }) .then(qApp, [p, oidIndex] (TableSizes sizes) { if (p) p.data()->PopulateSizes(oidIndex, sizes); }); } TablesTableModel::TableSizes TablesTableModel::QueryTableSizes() const { std::string nsfilter; switch (m_namespaceFilter) { case NamespaceFilter::User: nsfilter = "(NOT (nspname LIKE 'pg_%' OR nspname='information_schema'))"; break; case NamespaceFilter::PgCatalog: nsfilter = "nspname = 'pg_catalog'"; break; case NamespaceFilter::InformationSchema: nsfilter = "nspname = 'information_schema'"; break; } std::string q = "SELECT pg_class.oid, " " pg_total_relation_size(pg_class.oid) AS total_bytes, " " CASE WHEN relkind='r' THEN pg_indexes_size(pg_class.oid) ELSE 0 END AS index_bytes, " " CASE WHEN relkind='r' THEN pg_total_relation_size(reltoastrelid) ELSE 0 END AS toast_bytes " "\nFROM pg_catalog.pg_class \n" " JOIN pg_namespace ON (relnamespace = pg_namespace.oid)" " LEFT JOIN pg_catalog.pg_description AS d ON (objoid=pg_class.oid AND objsubid=0) \n" "WHERE relkind IN ('r', 'p', 'm') AND " + nsfilter; ; Pgsql::Connection connection; connection.connect(openDatabase->config().connectionString()); Pgsql::Result sizesResult = connection.query(q.c_str()); TablesTableModel::TableSizes sizes; sizes.reserve(sizesResult.rows()); for (const Pgsql::Row& row : sizesResult) { TableSize size; size.oid = row.get(0); size.totalBytes = row.get(1); size.indexBytes = row.get(2); size.toastBytes = row.get(3); sizes.push_back(size); } return sizes; } void TablesTableModel::PopulateSizes( const OidClassIndex &oidIndex, const std::vector &sizes ) { for (auto s : sizes) { auto findIter = oidIndex.find(s.oid); if (findIter != oidIndex.end()) { findIter->second->sizes = s; } } emit dataChanged( index(0, TotalSizeCol), index(static_cast(rootNode->children.size()), ToastSizeCol) ); } const TableNode* TablesTableModel::nodeFromIndex(const QModelIndex &index) { return static_cast(index.internalPointer()); } std::function TablesTableModel::GetNamespaceFilterLambda() { switch (m_namespaceFilter) { case NamespaceFilter::User: return [] (const PgClass &c) { return !c.ns().isSystemCatalog(); }; case NamespaceFilter::PgCatalog: return [] (const PgClass &c) { return c.ns().objectName() == "pg_catalog"; }; case NamespaceFilter::InformationSchema: return [] (const PgClass &c) { return c.ns().objectName() == "information_schema"; }; } throw std::logic_error("missing case"); }