Merge pull request #3305 from dpalou/MOBILE-4087
MOBILE-4087 debug: Allow printing tables db summarymain
commit
49bd11b39c
|
@ -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…
Reference in New Issue