MOBILE-3821 core: Improve database debugging DX
parent
1b8ada7b57
commit
bdd6e488b9
|
@ -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;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue