MOBILE-3821 core: Improve database debugging DX
parent
1b8ada7b57
commit
bdd6e488b9
|
@ -1170,27 +1170,54 @@ export class SQLiteDB {
|
||||||
*/
|
*/
|
||||||
protected getDatabaseSpies(db: SQLiteObject): Partial<SQLiteObject> {
|
protected getDatabaseSpies(db: SQLiteObject): Partial<SQLiteObject> {
|
||||||
return {
|
return {
|
||||||
executeSql(statement, params) {
|
async executeSql(statement, params) {
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
|
|
||||||
return db.executeSql(statement, params).then(result => {
|
try {
|
||||||
CoreDB.logQuery(statement, performance.now() - start, params);
|
const result = await db.executeSql(statement, params);
|
||||||
|
|
||||||
|
CoreDB.logQuery({
|
||||||
|
params,
|
||||||
|
sql: statement,
|
||||||
|
duration: performance.now() - start,
|
||||||
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
CoreDB.logQuery({
|
||||||
|
params,
|
||||||
|
error,
|
||||||
|
sql: statement,
|
||||||
|
duration: performance.now() - start,
|
||||||
});
|
});
|
||||||
},
|
|
||||||
sqlBatch(statements) {
|
|
||||||
const start = performance.now();
|
|
||||||
|
|
||||||
return db.sqlBatch(statements).then(result => {
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async sqlBatch(statements) {
|
||||||
|
const start = performance.now();
|
||||||
const sql = Array.isArray(statements)
|
const sql = Array.isArray(statements)
|
||||||
? statements.join(' | ')
|
? statements.join(' | ')
|
||||||
: String(statements);
|
: String(statements);
|
||||||
|
|
||||||
CoreDB.logQuery(sql, performance.now() - start);
|
try {
|
||||||
|
const result = await db.sqlBatch(statements);
|
||||||
|
|
||||||
|
CoreDB.logQuery({
|
||||||
|
sql,
|
||||||
|
duration: performance.now() - start,
|
||||||
|
});
|
||||||
|
|
||||||
return result;
|
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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
/* tslint:disable:no-console */
|
|
||||||
|
|
||||||
import { SQLiteDB } from '@classes/sqlitedb';
|
import { SQLiteDB } from '@classes/sqlitedb';
|
||||||
import { DbTransaction, SQLiteObject } from '@ionic-native/sqlite/ngx';
|
import { DbTransaction, SQLiteObject } from '@ionic-native/sqlite/ngx';
|
||||||
import { CoreDB } from '@services/db';
|
import { CoreDB } from '@services/db';
|
||||||
|
@ -53,7 +51,7 @@ export class SQLiteDBMock extends SQLiteDB {
|
||||||
await this.ready();
|
await this.ready();
|
||||||
|
|
||||||
return new Promise((resolve, reject): void => {
|
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.
|
// Query all tables from sqlite_master that we have created and can modify.
|
||||||
const args = [];
|
const args = [];
|
||||||
const query = `SELECT * FROM sqlite_master
|
const query = `SELECT * FROM sqlite_master
|
||||||
|
@ -99,15 +97,13 @@ export class SQLiteDBMock extends SQLiteDB {
|
||||||
|
|
||||||
return new Promise((resolve, reject): void => {
|
return new Promise((resolve, reject): void => {
|
||||||
// With WebSQL, all queries must be run in a transaction.
|
// With WebSQL, all queries must be run in a transaction.
|
||||||
this.db!.transaction((tx) => {
|
this.db?.transaction((tx) => {
|
||||||
tx.executeSql(sql, params, (tx, results) => {
|
tx.executeSql(
|
||||||
resolve(results);
|
sql,
|
||||||
}, (tx, error) => {
|
params,
|
||||||
// eslint-disable-next-line no-console
|
(_, results) => resolve(results),
|
||||||
console.error(sql, params, error);
|
(_, error) => reject(new Error(`SQL failed: ${sql}, reason: ${error?.message}`)),
|
||||||
|
);
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -126,7 +122,7 @@ export class SQLiteDBMock extends SQLiteDB {
|
||||||
|
|
||||||
return new Promise((resolve, reject): void => {
|
return new Promise((resolve, reject): void => {
|
||||||
// Create a transaction to execute the queries.
|
// Create a transaction to execute the queries.
|
||||||
this.db!.transaction((tx) => {
|
this.db?.transaction((tx) => {
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
// Execute all the queries. Each statement can be a string or an array.
|
// Execute all the queries. Each statement can be a string or an array.
|
||||||
|
@ -143,14 +139,7 @@ export class SQLiteDBMock extends SQLiteDB {
|
||||||
params = null;
|
params = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.executeSql(query, params, (tx, results) => {
|
tx.executeSql(query, params, (_, results) => resolve(results), (_, error) => reject(error));
|
||||||
resolve(results);
|
|
||||||
}, (tx, error) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(query, params, error);
|
|
||||||
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -187,13 +176,30 @@ export class SQLiteDBMock extends SQLiteDB {
|
||||||
const transactionSpy: DbTransaction = {
|
const transactionSpy: DbTransaction = {
|
||||||
executeSql(sql, params, success, error) {
|
executeSql(sql, params, success, error) {
|
||||||
const start = performance.now();
|
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.
|
// limitations under the License.
|
||||||
|
|
||||||
import { CoreConfig, CoreConfigProvider } from '@services/config';
|
import { CoreConfig, CoreConfigProvider } from '@services/config';
|
||||||
|
import { CoreDB, CoreDbProvider } from '@services/db';
|
||||||
import { CoreConstants } from '../constants';
|
import { CoreConstants } from '../constants';
|
||||||
|
|
||||||
type DevelopmentWindow = Window & {
|
type DevelopmentWindow = Window & {
|
||||||
configProvider?: CoreConfigProvider;
|
configProvider?: CoreConfigProvider;
|
||||||
|
dbProvider?: CoreDbProvider;
|
||||||
};
|
};
|
||||||
|
|
||||||
function initializeDevelopmentWindow(window: DevelopmentWindow) {
|
function initializeDevelopmentWindow(window: DevelopmentWindow) {
|
||||||
window.configProvider = CoreConfig.instance;
|
window.configProvider = CoreConfig.instance;
|
||||||
|
window.dbProvider = CoreDB.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function(): void {
|
export default function(): void {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { SQLiteDB } from '@classes/sqlitedb';
|
||||||
import { SQLiteDBMock } from '@features/emulator/classes/sqlitedb';
|
import { SQLiteDBMock } from '@features/emulator/classes/sqlitedb';
|
||||||
import { makeSingleton, SQLite, Platform } from '@singletons';
|
import { makeSingleton, SQLite, Platform } from '@singletons';
|
||||||
import { CoreAppProvider } from './app';
|
import { CoreAppProvider } from './app';
|
||||||
|
import { CoreUtils } from './utils/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
@ -35,15 +36,21 @@ export class CoreDbProvider {
|
||||||
* @returns Whether queries should be logged.
|
* @returns Whether queries should be logged.
|
||||||
*/
|
*/
|
||||||
loggingEnabled(): boolean {
|
loggingEnabled(): boolean {
|
||||||
return CoreAppProvider.isAutomated();
|
return CoreUtils.hasCookie('MoodleAppDBLoggingEnabled') || CoreAppProvider.isAutomated();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print query history in console.
|
* Print query history in console.
|
||||||
|
*
|
||||||
|
* @param format Log format, with the following substitutions: :sql, :duration, and :result.
|
||||||
*/
|
*/
|
||||||
printHistory(): void {
|
printHistory(format: string = ':sql | Duration: :duration | Result: :result'): void {
|
||||||
const substituteParams = ({ sql, params }: CoreDbQueryLog) =>
|
const substituteParams = ({ sql, params, duration, error }: CoreDbQueryLog) => format
|
||||||
Object.values(params ?? []).reduce((sql: string, param: string) => sql.replace('?', param), sql);
|
.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
|
// eslint-disable-next-line no-console
|
||||||
console.log(this.queryLogs.map(substituteParams).join('\n'));
|
console.log(this.queryLogs.map(substituteParams).join('\n'));
|
||||||
|
@ -52,11 +59,10 @@ export class CoreDbProvider {
|
||||||
/**
|
/**
|
||||||
* Log a query.
|
* Log a query.
|
||||||
*
|
*
|
||||||
* @param sql Query SQL.
|
* @param log Query log.
|
||||||
* @param params Query parameters.
|
|
||||||
*/
|
*/
|
||||||
logQuery(sql: string, duration: number, params?: unknown[]): void {
|
logQuery(log: CoreDbQueryLog): void {
|
||||||
this.queryLogs.push({ sql, duration, params });
|
this.queryLogs.push(log);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -121,5 +127,6 @@ export const CoreDB = makeSingleton(CoreDbProvider);
|
||||||
export interface CoreDbQueryLog {
|
export interface CoreDbQueryLog {
|
||||||
sql: string;
|
sql: string;
|
||||||
duration: number;
|
duration: number;
|
||||||
|
error?: Error;
|
||||||
params?: unknown[];
|
params?: unknown[];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue