From 4e221ecd3a63a49972c8928d6e3968549e1eac45 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 10 Feb 2022 16:07:44 +0100 Subject: [PATCH] show table row count for MySQL --- .../src/controllers/databaseConnections.js | 3 +- .../api/src/proc/databaseConnectionProcess.js | 8 ++- packages/tools/src/DatabaseAnalyser.ts | 56 ++++++++++++++++++- packages/types/dbinfo.d.ts | 1 + packages/types/index.d.ts | 1 + .../src/appobj/DatabaseObjectAppObject.svelte | 1 + .../src/backend/Analyser.js | 2 + .../src/backend/sql/tableModifications.js | 1 + .../src/backend/sql/tables.js | 1 + 9 files changed, 69 insertions(+), 5 deletions(-) diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js index d0911adc..68dd2649 100644 --- a/packages/api/src/controllers/databaseConnections.js +++ b/packages/api/src/controllers/databaseConnections.js @@ -62,9 +62,10 @@ module.exports = { delete this.requests[msgid]; }, handle_status(conid, database, { status }) { + // console.log('HANDLE SET STATUS', status); const existing = this.opened.find(x => x.conid == conid && x.database == database); if (!existing) return; - if (existing.status == status) return; + if (existing.status && status && existing.status.counter > status.counter) return; existing.status = status; socket.emitChanged(`database-status-changed-${conid}-${database}`); }, diff --git a/packages/api/src/proc/databaseConnectionProcess.js b/packages/api/src/proc/databaseConnectionProcess.js index a9f1422a..c3ae8a3e 100644 --- a/packages/api/src/proc/databaseConnectionProcess.js +++ b/packages/api/src/proc/databaseConnectionProcess.js @@ -18,6 +18,12 @@ let lastStatus = null; let analysedTime = 0; let serverVersion; +let statusCounter = 0; +function getStatusCounter() { + statusCounter += 1; + return statusCounter; +} + async function checkedAsyncCall(promise) { try { const res = await promise; @@ -79,7 +85,7 @@ function handleSyncModel() { function setStatus(status) { const statusString = stableStringify(status); if (lastStatus != statusString) { - process.send({ msgtype: 'status', status }); + process.send({ msgtype: 'status', status: { ...status, counter: getStatusCounter() } }); lastStatus = statusString; } } diff --git a/packages/tools/src/DatabaseAnalyser.ts b/packages/tools/src/DatabaseAnalyser.ts index e0dfd097..9c454854 100644 --- a/packages/tools/src/DatabaseAnalyser.ts +++ b/packages/tools/src/DatabaseAnalyser.ts @@ -8,6 +8,26 @@ const STRUCTURE_FIELDS = ['tables', 'collections', 'views', 'matviews', 'functio const fp_pick = arg => array => _pick(array, arg); +function mergeTableRowCounts(info: DatabaseInfo, rowCounts): DatabaseInfo { + return { + ...info, + tables: (info.tables || []).map(table => ({ + ...table, + tableRowCount: rowCounts.find(x => x.objectId == table.objectId)?.tableRowCount ?? table.tableRowCount, + })), + }; +} + +function areDifferentRowCounts(db1: DatabaseInfo, db2: DatabaseInfo) { + for (const t1 of db1.tables || []) { + const t2 = (db2.tables || []).find(x => x.objectId == t1.objectId); + if (t1?.tableRowCount !== t2?.tableRowCount) { + return true; + } + } + return false; +} + export class DatabaseAnalyser { structure: DatabaseInfo; modifications: DatabaseModification[]; @@ -64,13 +84,29 @@ export class DatabaseAnalyser { async incrementalAnalysis(structure) { this.structure = structure; - this.modifications = await this.getModifications(); - if (this.modifications == null) { + const modifications = await this.getModifications(); + if (modifications == null) { // modifications not implemented, perform full analysis this.structure = null; return this.addEngineField(await this._runAnalysis()); } - if (this.modifications.length == 0) return null; + const structureModifications = modifications.filter(x => x.action != 'setTableRowCounts'); + const setTableRowCounts = modifications.find(x => x.action == 'setTableRowCounts'); + + let structureWithRowCounts = null; + if (setTableRowCounts) { + const newStructure = mergeTableRowCounts(structure, setTableRowCounts.rowCounts); + if (areDifferentRowCounts(structure, newStructure)) { + structureWithRowCounts = newStructure; + } + } + + if (structureModifications.length == 0) { + return structureWithRowCounts ? this.addEngineField(structureWithRowCounts) : null; + } + + this.modifications = structureModifications; + if (structureWithRowCounts) this.structure = structureWithRowCounts; console.log('DB modifications detected:', this.modifications); return this.addEngineField(this.mergeAnalyseResult(await this._runAnalysis())); } @@ -226,6 +262,20 @@ export class DatabaseAnalyser { } } + const rowCounts = (snapshot.tables || []) + .filter(x => x.tableRowCount != null) + .map(x => ({ + objectId: x.objectId, + tableRowCount: x.tableRowCount, + })); + + if (rowCounts.length > 0) { + res.push({ + action: 'setTableRowCounts', + rowCounts, + }); + } + return [..._compact(res), ...this.getDeletedObjects(snapshot)]; } diff --git a/packages/types/dbinfo.d.ts b/packages/types/dbinfo.d.ts index 0793b8d8..ad70e22c 100644 --- a/packages/types/dbinfo.d.ts +++ b/packages/types/dbinfo.d.ts @@ -85,6 +85,7 @@ export interface TableInfo extends DatabaseObjectInfo { preloadedRows?: any[]; preloadedRowsKey?: string[]; preloadedRowsInsertOnly?: string[]; + tableRowCount?: number | string; __isDynamicStructure?: boolean; } diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts index e7f8ad0a..48dd47e3 100644 --- a/packages/types/index.d.ts +++ b/packages/types/index.d.ts @@ -11,6 +11,7 @@ export interface OpenedDatabaseConnection { status?: { name: string; message?: string; + counter: number; }; } diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.svelte b/packages/web/src/appobj/DatabaseObjectAppObject.svelte index ff2e3653..9ae3ff97 100644 --- a/packages/web/src/appobj/DatabaseObjectAppObject.svelte +++ b/packages/web/src/appobj/DatabaseObjectAppObject.svelte @@ -627,6 +627,7 @@ showPinnedInsteadOfUnpin={passProps?.showPinnedInsteadOfUnpin} onPin={isPinned ? null : () => pinnedTables.update(list => [...list, data])} onUnpin={isPinned ? () => pinnedTables.update(list => list.filter(x => !testEqual(x, data))) : null} + extInfo={data.tableRowCount != null ? `${data.tableRowCount} rows` : null} on:click={() => handleClick()} on:middleclick={() => handleClick(true)} on:expand diff --git a/plugins/dbgate-plugin-mysql/src/backend/Analyser.js b/plugins/dbgate-plugin-mysql/src/backend/Analyser.js index 5be38c86..b63a90ca 100644 --- a/plugins/dbgate-plugin-mysql/src/backend/Analyser.js +++ b/plugins/dbgate-plugin-mysql/src/backend/Analyser.js @@ -88,6 +88,7 @@ class Analyser extends DatabaseAnalyser { columns: columns.rows.filter(col => col.pureName == table.pureName).map(getColumnInfo), primaryKey: DatabaseAnalyser.extractPrimaryKeys(table, pkColumns.rows), foreignKeys: DatabaseAnalyser.extractForeignKeys(table, fkColumns.rows), + tableRowCount: table.tableRowCount, indexes: _.uniqBy( indexes.rows.filter( idx => @@ -163,6 +164,7 @@ class Analyser extends DatabaseAnalyser { ...x, objectId: x.pureName, contentHash: _.isDate(x.modifyDate) ? x.modifyDate.toISOString() : x.modifyDate, + tableRowCount: x.tableRowCount, })), views: tableModificationsQueryData.rows .filter(x => x.objectType == 'VIEW') diff --git a/plugins/dbgate-plugin-mysql/src/backend/sql/tableModifications.js b/plugins/dbgate-plugin-mysql/src/backend/sql/tableModifications.js index 6382ea5a..3925a298 100644 --- a/plugins/dbgate-plugin-mysql/src/backend/sql/tableModifications.js +++ b/plugins/dbgate-plugin-mysql/src/backend/sql/tableModifications.js @@ -2,6 +2,7 @@ module.exports = ` select TABLE_NAME as pureName, TABLE_TYPE as objectType, + TABLE_ROWS as tableRowCount, case when ENGINE='InnoDB' then CREATE_TIME else coalesce(UPDATE_TIME, CREATE_TIME) end as modifyDate from information_schema.tables where TABLE_SCHEMA = '#DATABASE#' diff --git a/plugins/dbgate-plugin-mysql/src/backend/sql/tables.js b/plugins/dbgate-plugin-mysql/src/backend/sql/tables.js index 1bbff4b3..86c74ccd 100644 --- a/plugins/dbgate-plugin-mysql/src/backend/sql/tables.js +++ b/plugins/dbgate-plugin-mysql/src/backend/sql/tables.js @@ -1,6 +1,7 @@ module.exports = ` select TABLE_NAME as pureName, + TABLE_ROWS as tableRowCount, case when ENGINE='InnoDB' then CREATE_TIME else coalesce(UPDATE_TIME, CREATE_TIME) end as modifyDate from information_schema.tables where TABLE_SCHEMA = '#DATABASE#' and TABLE_TYPE='BASE TABLE' and TABLE_NAME =OBJECT_ID_CONDITION;