MOBILE-4087 debug: Allow printing tables db summary
This commit is contained in:
		
							parent
							
								
									3f5f99c645
								
							
						
					
					
						commit
						29889169f5
					
				| @ -1169,6 +1169,8 @@ export class SQLiteDB { | |||||||
|      * @returns Spy methods. |      * @returns Spy methods. | ||||||
|      */ |      */ | ||||||
|     protected getDatabaseSpies(db: SQLiteObject): Partial<SQLiteObject> { |     protected getDatabaseSpies(db: SQLiteObject): Partial<SQLiteObject> { | ||||||
|  |         const dbName = this.name; | ||||||
|  | 
 | ||||||
|         return { |         return { | ||||||
|             async executeSql(statement, params) { |             async executeSql(statement, params) { | ||||||
|                 const start = performance.now(); |                 const start = performance.now(); | ||||||
| @ -1180,6 +1182,7 @@ export class SQLiteDB { | |||||||
|                         params, |                         params, | ||||||
|                         sql: statement, |                         sql: statement, | ||||||
|                         duration:  performance.now() - start, |                         duration:  performance.now() - start, | ||||||
|  |                         dbName, | ||||||
|                     }); |                     }); | ||||||
| 
 | 
 | ||||||
|                     return result; |                     return result; | ||||||
| @ -1189,6 +1192,7 @@ export class SQLiteDB { | |||||||
|                         error, |                         error, | ||||||
|                         sql: statement, |                         sql: statement, | ||||||
|                         duration:  performance.now() - start, |                         duration:  performance.now() - start, | ||||||
|  |                         dbName, | ||||||
|                     }); |                     }); | ||||||
| 
 | 
 | ||||||
|                     throw error; |                     throw error; | ||||||
| @ -1206,6 +1210,7 @@ export class SQLiteDB { | |||||||
|                     CoreDB.logQuery({ |                     CoreDB.logQuery({ | ||||||
|                         sql, |                         sql, | ||||||
|                         duration: performance.now() - start, |                         duration: performance.now() - start, | ||||||
|  |                         dbName, | ||||||
|                     }); |                     }); | ||||||
| 
 | 
 | ||||||
|                     return result; |                     return result; | ||||||
| @ -1214,6 +1219,7 @@ export class SQLiteDB { | |||||||
|                         sql, |                         sql, | ||||||
|                         error, |                         error, | ||||||
|                         duration: performance.now() - start, |                         duration: performance.now() - start, | ||||||
|  |                         dbName, | ||||||
|                     }); |                     }); | ||||||
| 
 | 
 | ||||||
|                     throw error; |                     throw error; | ||||||
|  | |||||||
| @ -171,6 +171,8 @@ export class SQLiteDBMock extends SQLiteDB { | |||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     protected getDatabaseSpies(db: SQLiteObject): Partial<SQLiteObject> { |     protected getDatabaseSpies(db: SQLiteObject): Partial<SQLiteObject> { | ||||||
|  |         const dbName = this.name; | ||||||
|  | 
 | ||||||
|         return { |         return { | ||||||
|             transaction: (callback) => db.transaction((transaction) => { |             transaction: (callback) => db.transaction((transaction) => { | ||||||
|                 const transactionSpy: DbTransaction = { |                 const transactionSpy: DbTransaction = { | ||||||
| @ -185,6 +187,7 @@ export class SQLiteDBMock extends SQLiteDB { | |||||||
|                                     sql, |                                     sql, | ||||||
|                                     params, |                                     params, | ||||||
|                                     duration: performance.now() - start, |                                     duration: performance.now() - start, | ||||||
|  |                                     dbName, | ||||||
|                                 }); |                                 }); | ||||||
| 
 | 
 | ||||||
|                                 return success?.(...args); |                                 return success?.(...args); | ||||||
| @ -195,6 +198,7 @@ export class SQLiteDBMock extends SQLiteDB { | |||||||
|                                     params, |                                     params, | ||||||
|                                     error: args[0], |                                     error: args[0], | ||||||
|                                     duration: performance.now() - start, |                                     duration: performance.now() - start, | ||||||
|  |                                     dbName, | ||||||
|                                 }); |                                 }); | ||||||
| 
 | 
 | ||||||
|                                 return error?.(...args); |                                 return error?.(...args); | ||||||
|  | |||||||
| @ -20,6 +20,16 @@ import { CoreBrowser } from '@singletons/browser'; | |||||||
| import { makeSingleton, SQLite, Platform } from '@singletons'; | import { makeSingleton, SQLite, Platform } from '@singletons'; | ||||||
| import { CoreAppProvider } from './app'; | import { CoreAppProvider } from './app'; | ||||||
| 
 | 
 | ||||||
|  | const tableNameRegex = new RegExp([ | ||||||
|  |     '^SELECT.*FROM ([^ ]+)', | ||||||
|  |     '^INSERT.*INTO ([^ ]+)', | ||||||
|  |     '^UPDATE ([^ ]+)', | ||||||
|  |     '^DELETE FROM ([^ ]+)', | ||||||
|  |     '^CREATE TABLE IF NOT EXISTS ([^ ]+)', | ||||||
|  |     '^ALTER TABLE ([^ ]+)', | ||||||
|  |     '^DROP TABLE IF EXISTS ([^ ]+)', | ||||||
|  | ].join('|')); | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * This service allows interacting with the local database to store and retrieve data. |  * This service allows interacting with the local database to store and retrieve data. | ||||||
|  */ |  */ | ||||||
| @ -42,10 +52,11 @@ export class CoreDbProvider { | |||||||
|     /** |     /** | ||||||
|      * Print query history in console. |      * Print query history in console. | ||||||
|      * |      * | ||||||
|      * @param format Log format, with the following substitutions: :sql, :duration, and :result. |      * @param format Log format, with the following substitutions: :dbname, :sql, :duration, and :result. | ||||||
|      */ |      */ | ||||||
|     printHistory(format: string = ':sql | Duration: :duration | Result: :result'): void { |     printHistory(format: string = ':dbname | :sql | Duration: :duration | Result: :result'): void { | ||||||
|         const substituteParams = ({ sql, params, duration, error }: CoreDbQueryLog) => format |         const substituteParams = ({ sql, params, duration, error, dbName }: CoreDbQueryLog) => format | ||||||
|  |             .replace(':dbname', dbName) | ||||||
|             .replace(':sql', Object |             .replace(':sql', Object | ||||||
|                 .values(params ?? []) |                 .values(params ?? []) | ||||||
|                 .reduce((sql: string, param: string) => sql.replace('?', param) as string, sql) as string) |                 .reduce((sql: string, param: string) => sql.replace('?', param) as string, sql) as string) | ||||||
| @ -56,6 +67,120 @@ export class CoreDbProvider { | |||||||
|         console.log(this.queryLogs.map(substituteParams).join('\n')); |         console.log(this.queryLogs.map(substituteParams).join('\n')); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the table name from a SQL query. | ||||||
|  |      * | ||||||
|  |      * @param sql SQL query. | ||||||
|  |      * @return Table name, undefined if not found. | ||||||
|  |      */ | ||||||
|  |     protected getTableNameFromSql(sql: string): string | undefined { | ||||||
|  |         const matches = sql.match(tableNameRegex); | ||||||
|  | 
 | ||||||
|  |         return matches?.find((matchEntry, index) => index > 0 && !!matchEntry); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if a value matches a certain filter. | ||||||
|  |      * | ||||||
|  |      * @param value Value. | ||||||
|  |      * @param filter Filter. | ||||||
|  |      * @return Whether the value matches the filter. | ||||||
|  |      */ | ||||||
|  |     protected valueMatchesFilter(value: string, filter?: RegExp | string): boolean { | ||||||
|  |         if (typeof filter === 'string') { | ||||||
|  |             return value === filter; | ||||||
|  |         } else if (filter) { | ||||||
|  |             return !!value.match(filter); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Build an object with the summary data for each db, table and statement. | ||||||
|  |      * | ||||||
|  |      * @param filters Filters to limit the data stored. | ||||||
|  |      * @return Object with the summary data. | ||||||
|  |      */ | ||||||
|  |     protected buildStatementsSummary( | ||||||
|  |         filters: TablesSummaryFilters = {}, | ||||||
|  |     ): Record<string, Record<string, Record<string, CoreDbStatementSummary>>> { | ||||||
|  |         const statementsSummary: Record<string, Record<string, Record<string, CoreDbStatementSummary>>> = {}; | ||||||
|  | 
 | ||||||
|  |         this.queryLogs.forEach(log => { | ||||||
|  |             if (!this.valueMatchesFilter(log.dbName, filters.dbName)) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const statement = log.sql.substring(0, log.sql.indexOf(' ')); | ||||||
|  |             if (!statement) { | ||||||
|  |                 console.warn(`Statement not found from sql: ${log.sql}`); // eslint-disable-line no-console
 | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const tableName = this.getTableNameFromSql(log.sql); | ||||||
|  |             if (!tableName) { | ||||||
|  |                 console.warn(`Table name not found from sql: ${log.sql}`); // eslint-disable-line no-console
 | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!this.valueMatchesFilter(tableName, filters.tableName)) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             statementsSummary[log.dbName] = statementsSummary[log.dbName] ?? {}; | ||||||
|  |             statementsSummary[log.dbName][tableName] = statementsSummary[log.dbName][tableName] ?? {}; | ||||||
|  |             statementsSummary[log.dbName][tableName][statement] = statementsSummary[log.dbName][tableName][statement] ?? { | ||||||
|  |                 count: 0, | ||||||
|  |                 duration: 0, | ||||||
|  |                 errors: 0, | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             statementsSummary[log.dbName][tableName][statement].count++; | ||||||
|  |             statementsSummary[log.dbName][tableName][statement].duration += log.duration; | ||||||
|  |             if (log.error) { | ||||||
|  |                 statementsSummary[log.dbName][tableName][statement].errors++; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return statementsSummary; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Print summary of statements for several tables. | ||||||
|  |      * | ||||||
|  |      * @param filters Filters to limit the results printed. | ||||||
|  |      * @param format Log format, with the following substitutions: :dbname, :table, :statement, :count, :duration and :errors. | ||||||
|  |      */ | ||||||
|  |     printTablesSummary( | ||||||
|  |         filters: TablesSummaryFilters = {}, | ||||||
|  |         format = ':dbname, :table, :statement, :count, :duration, :errors', | ||||||
|  |     ): void { | ||||||
|  |         const statementsSummary = this.buildStatementsSummary(filters); | ||||||
|  | 
 | ||||||
|  |         const substituteParams = (dbName: string,  tableName: string, statementName: string) => format | ||||||
|  |             .replace(':dbname', dbName) | ||||||
|  |             .replace(':table', tableName) | ||||||
|  |             .replace(':statement', statementName) | ||||||
|  |             .replace(':count', String(statementsSummary[dbName][tableName][statementName].count)) | ||||||
|  |             .replace(':duration', statementsSummary[dbName][tableName][statementName].duration + 'ms') | ||||||
|  |             .replace(':errors', String(statementsSummary[dbName][tableName][statementName].errors)); | ||||||
|  | 
 | ||||||
|  |         // eslint-disable-next-line no-console
 | ||||||
|  |         console.log( | ||||||
|  |             Object.keys(statementsSummary) | ||||||
|  |                 .sort() | ||||||
|  |                 .map(dbName => Object.keys(statementsSummary[dbName]) | ||||||
|  |                     .sort() | ||||||
|  |                     .map(tableName => Object.keys(statementsSummary[dbName][tableName]) | ||||||
|  |                         .sort() | ||||||
|  |                         .map(statementName => substituteParams(dbName, tableName, statementName)) | ||||||
|  |                         .join('\n')).join('\n')).join('\n'), | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Log a query. |      * Log a query. | ||||||
|      * |      * | ||||||
| @ -65,6 +190,13 @@ export class CoreDbProvider { | |||||||
|         this.queryLogs.push(log); |         this.queryLogs.push(log); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Clear stored logs. | ||||||
|  |      */ | ||||||
|  |     clearLogs(): void { | ||||||
|  |         this.queryLogs = []; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Get or create a database object. |      * Get or create a database object. | ||||||
|      * |      * | ||||||
| @ -125,8 +257,26 @@ export const CoreDB = makeSingleton(CoreDbProvider); | |||||||
|  * Database query log entry. |  * Database query log entry. | ||||||
|  */ |  */ | ||||||
| export interface CoreDbQueryLog { | export interface CoreDbQueryLog { | ||||||
|  |     dbName: string; | ||||||
|     sql: string; |     sql: string; | ||||||
|     duration: number; |     duration: number; | ||||||
|     error?: Error; |     error?: Error; | ||||||
|     params?: unknown[]; |     params?: unknown[]; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Summary about a certain DB statement. | ||||||
|  |  */ | ||||||
|  | type CoreDbStatementSummary = { | ||||||
|  |     count: number; | ||||||
|  |     duration: number; | ||||||
|  |     errors: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Filters to print tables summary. | ||||||
|  |  */ | ||||||
|  | type TablesSummaryFilters = { | ||||||
|  |     dbName?: RegExp | string; | ||||||
|  |     tableName?: RegExp | string; | ||||||
|  | }; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user