MOBILE-3821 core: Improve database debugging DX

main
Noel De Martin 2022-02-17 13:54:54 +01:00
parent 1b8ada7b57
commit bdd6e488b9
4 changed files with 88 additions and 45 deletions

View File

@ -1170,27 +1170,54 @@ export class SQLiteDB {
*/
protected getDatabaseSpies(db: SQLiteObject): Partial<SQLiteObject> {
return {
executeSql(statement, params) {
async executeSql(statement, params) {
const start = performance.now();
return db.executeSql(statement, params).then(result => {
CoreDB.logQuery(statement, performance.now() - start, params);
try {
const result = await db.executeSql(statement, params);
CoreDB.logQuery({
params,
sql: statement,
duration: performance.now() - start,
});
return result;
});
} catch (error) {
CoreDB.logQuery({
params,
error,
sql: statement,
duration: performance.now() - start,
});
throw error;
}
},
sqlBatch(statements) {
async sqlBatch(statements) {
const start = performance.now();
const sql = Array.isArray(statements)
? statements.join(' | ')
: String(statements);
return db.sqlBatch(statements).then(result => {
const sql = Array.isArray(statements)
? statements.join(' | ')
: String(statements);
try {
const result = await db.sqlBatch(statements);
CoreDB.logQuery(sql, performance.now() - start);
CoreDB.logQuery({
sql,
duration: performance.now() - start,
});
return result;
});
} catch (error) {
CoreDB.logQuery({
sql,
error,
duration: performance.now() - start,
});
throw error;
}
},
};
}

View File

@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
/* tslint:disable:no-console */
import { SQLiteDB } from '@classes/sqlitedb';
import { DbTransaction, SQLiteObject } from '@ionic-native/sqlite/ngx';
import { CoreDB } from '@services/db';
@ -53,7 +51,7 @@ export class SQLiteDBMock extends SQLiteDB {
await this.ready();
return new Promise((resolve, reject): void => {
this.db!.transaction((tx) => {
this.db?.transaction((tx) => {
// Query all tables from sqlite_master that we have created and can modify.
const args = [];
const query = `SELECT * FROM sqlite_master
@ -99,15 +97,13 @@ export class SQLiteDBMock extends SQLiteDB {
return new Promise((resolve, reject): void => {
// With WebSQL, all queries must be run in a transaction.
this.db!.transaction((tx) => {
tx.executeSql(sql, params, (tx, results) => {
resolve(results);
}, (tx, error) => {
// eslint-disable-next-line no-console
console.error(sql, params, error);
reject(error);
});
this.db?.transaction((tx) => {
tx.executeSql(
sql,
params,
(_, results) => resolve(results),
(_, error) => reject(new Error(`SQL failed: ${sql}, reason: ${error?.message}`)),
);
});
});
}
@ -126,7 +122,7 @@ export class SQLiteDBMock extends SQLiteDB {
return new Promise((resolve, reject): void => {
// Create a transaction to execute the queries.
this.db!.transaction((tx) => {
this.db?.transaction((tx) => {
const promises: Promise<void>[] = [];
// Execute all the queries. Each statement can be a string or an array.
@ -143,14 +139,7 @@ export class SQLiteDBMock extends SQLiteDB {
params = null;
}
tx.executeSql(query, params, (tx, results) => {
resolve(results);
}, (tx, error) => {
// eslint-disable-next-line no-console
console.error(query, params, error);
reject(error);
});
tx.executeSql(query, params, (_, results) => resolve(results), (_, error) => reject(error));
}));
});
@ -187,13 +176,30 @@ export class SQLiteDBMock extends SQLiteDB {
const transactionSpy: DbTransaction = {
executeSql(sql, params, success, error) {
const start = performance.now();
const resolve = callback => (...args) => {
CoreDB.logQuery(sql, performance.now() - start, params);
return callback(...args);
};
return transaction.executeSql(
sql,
params,
(...args) => {
CoreDB.logQuery({
sql,
params,
duration: performance.now() - start,
});
return transaction.executeSql(sql, params, resolve(success), resolve(error));
return success?.(...args);
},
(...args) => {
CoreDB.logQuery({
sql,
params,
error: args[0],
duration: performance.now() - start,
});
return error?.(...args);
},
);
},
};

View File

@ -13,14 +13,17 @@
// limitations under the License.
import { CoreConfig, CoreConfigProvider } from '@services/config';
import { CoreDB, CoreDbProvider } from '@services/db';
import { CoreConstants } from '../constants';
type DevelopmentWindow = Window & {
configProvider?: CoreConfigProvider;
dbProvider?: CoreDbProvider;
};
function initializeDevelopmentWindow(window: DevelopmentWindow) {
window.configProvider = CoreConfig.instance;
window.dbProvider = CoreDB.instance;
}
export default function(): void {

View File

@ -18,6 +18,7 @@ import { SQLiteDB } from '@classes/sqlitedb';
import { SQLiteDBMock } from '@features/emulator/classes/sqlitedb';
import { makeSingleton, SQLite, Platform } from '@singletons';
import { CoreAppProvider } from './app';
import { CoreUtils } from './utils/utils';
/**
* This service allows interacting with the local database to store and retrieve data.
@ -35,15 +36,21 @@ export class CoreDbProvider {
* @returns Whether queries should be logged.
*/
loggingEnabled(): boolean {
return CoreAppProvider.isAutomated();
return CoreUtils.hasCookie('MoodleAppDBLoggingEnabled') || CoreAppProvider.isAutomated();
}
/**
* Print query history in console.
*
* @param format Log format, with the following substitutions: :sql, :duration, and :result.
*/
printHistory(): void {
const substituteParams = ({ sql, params }: CoreDbQueryLog) =>
Object.values(params ?? []).reduce((sql: string, param: string) => sql.replace('?', param), sql);
printHistory(format: string = ':sql | Duration: :duration | Result: :result'): void {
const substituteParams = ({ sql, params, duration, error }: CoreDbQueryLog) => format
.replace(':sql', Object
.values(params ?? [])
.reduce((sql: string, param: string) => sql.replace('?', param) as string, sql) as string)
.replace(':duration', `${Math.round(duration).toString().padStart(4, '0')}ms`)
.replace(':result', error?.message ?? 'Success');
// eslint-disable-next-line no-console
console.log(this.queryLogs.map(substituteParams).join('\n'));
@ -52,11 +59,10 @@ export class CoreDbProvider {
/**
* Log a query.
*
* @param sql Query SQL.
* @param params Query parameters.
* @param log Query log.
*/
logQuery(sql: string, duration: number, params?: unknown[]): void {
this.queryLogs.push({ sql, duration, params });
logQuery(log: CoreDbQueryLog): void {
this.queryLogs.push(log);
}
/**
@ -121,5 +127,6 @@ export const CoreDB = makeSingleton(CoreDbProvider);
export interface CoreDbQueryLog {
sql: string;
duration: number;
error?: Error;
params?: unknown[];
}