Merge pull request #3127 from NoelDeMartin/MOBILE-3821
MOBILE-3821: Database optimization final tweaks (before 4.0)main
commit
5b0abf40f7
|
@ -54,18 +54,21 @@ export class AddonMessagesMainMenuHandlerService implements CoreMainMenuHandler,
|
|||
protected unreadCount = 0;
|
||||
protected contactRequestsCount = 0;
|
||||
protected orMore = false;
|
||||
protected badgeCount?: number;
|
||||
|
||||
constructor() {
|
||||
|
||||
CoreEvents.on(AddonMessagesProvider.UNREAD_CONVERSATION_COUNTS_EVENT, (data) => {
|
||||
this.unreadCount = data.favourites + data.individual + data.group + data.self;
|
||||
this.orMore = !!data.orMore;
|
||||
this.updateBadge(data.siteId!);
|
||||
|
||||
data.siteId && this.updateBadge(data.siteId);
|
||||
});
|
||||
|
||||
CoreEvents.on(AddonMessagesProvider.CONTACT_REQUESTS_COUNT_EVENT, (data) => {
|
||||
this.contactRequestsCount = data.count;
|
||||
this.updateBadge(data.siteId!);
|
||||
|
||||
data.siteId && this.updateBadge(data.siteId);
|
||||
});
|
||||
|
||||
// Reset info on logout.
|
||||
|
@ -123,27 +126,28 @@ export class AddonMessagesMainMenuHandlerService implements CoreMainMenuHandler,
|
|||
* @return Resolve when done.
|
||||
*/
|
||||
async refreshBadge(siteId?: string, unreadOnly?: boolean): Promise<void> {
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
if (!siteId) {
|
||||
const badgeSiteId = siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
if (!badgeSiteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
promises.push(AddonMessages.refreshUnreadConversationCounts(siteId).catch(() => {
|
||||
promises.push(AddonMessages.refreshUnreadConversationCounts(badgeSiteId).catch(() => {
|
||||
this.unreadCount = 0;
|
||||
this.orMore = false;
|
||||
}));
|
||||
|
||||
// Refresh the number of contact requests in 3.6+ sites.
|
||||
if (!unreadOnly && AddonMessages.isGroupMessagingEnabled()) {
|
||||
promises.push(AddonMessages.refreshContactRequestsCount(siteId).catch(() => {
|
||||
promises.push(AddonMessages.refreshContactRequestsCount(badgeSiteId).catch(() => {
|
||||
this.contactRequestsCount = 0;
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(promises).finally(() => {
|
||||
this.updateBadge(siteId!);
|
||||
this.updateBadge(badgeSiteId);
|
||||
this.handler.loading = false;
|
||||
});
|
||||
}
|
||||
|
@ -155,6 +159,13 @@ export class AddonMessagesMainMenuHandlerService implements CoreMainMenuHandler,
|
|||
*/
|
||||
updateBadge(siteId: string): void {
|
||||
const totalCount = this.unreadCount + (this.contactRequestsCount || 0);
|
||||
|
||||
if (this.badgeCount === totalCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.badgeCount = totalCount;
|
||||
|
||||
if (totalCount > 0) {
|
||||
this.handler.badge = totalCount + (this.orMore ? '+' : '');
|
||||
} else {
|
||||
|
|
|
@ -69,6 +69,7 @@ export class AddonModBookProvider {
|
|||
() => CoreSites.getSiteTable(LAST_CHAPTER_VIEWED_TABLE, {
|
||||
siteId,
|
||||
config: { cachingStrategy: CoreDatabaseCachingStrategy.None },
|
||||
onDestroy: () => delete this.lastChapterViewedTables[siteId],
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -14,10 +14,11 @@
|
|||
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
import { asyncInstance } from '@/core/utils/async-instance';
|
||||
import { SQLiteDB, SQLiteDBRecordValues } from '@classes/sqlitedb';
|
||||
import { SQLiteDBRecordValues } from '@classes/sqlitedb';
|
||||
import { CoreConfig, CoreConfigProvider } from '@services/config';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import {
|
||||
CoreDatabaseConfiguration,
|
||||
CoreDatabaseReducer,
|
||||
CoreDatabaseTable,
|
||||
CoreDatabaseConditions,
|
||||
|
@ -40,7 +41,8 @@ export class CoreDatabaseTableProxy<
|
|||
PrimaryKey extends GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> = GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn>
|
||||
> extends CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey> {
|
||||
|
||||
protected config: CoreDatabaseConfiguration;
|
||||
protected readonly DEFAULT_CACHING_STRATEGY = CoreDatabaseCachingStrategy.None;
|
||||
|
||||
protected target = asyncInstance<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>>();
|
||||
protected environmentObserver?: CoreEventObserver;
|
||||
protected targetConstructors: Record<
|
||||
|
@ -52,21 +54,12 @@ export class CoreDatabaseTableProxy<
|
|||
[CoreDatabaseCachingStrategy.None]: CoreDatabaseTable,
|
||||
};
|
||||
|
||||
constructor(
|
||||
config: Partial<CoreDatabaseConfiguration>,
|
||||
database: SQLiteDB,
|
||||
tableName: string,
|
||||
primaryKeyColumns?: PrimaryKeyColumn[],
|
||||
) {
|
||||
super(database, tableName, primaryKeyColumns);
|
||||
|
||||
this.config = { ...this.getConfigDefaults(), ...config };
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
await super.initialize();
|
||||
|
||||
this.environmentObserver = CoreEvents.on(CoreConfigProvider.ENVIRONMENT_UPDATED, async () => {
|
||||
if (!(await this.shouldUpdateTarget())) {
|
||||
return;
|
||||
|
@ -82,9 +75,23 @@ export class CoreDatabaseTableProxy<
|
|||
* @inheritdoc
|
||||
*/
|
||||
async destroy(): Promise<void> {
|
||||
await super.destroy();
|
||||
|
||||
this.environmentObserver?.off();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
matchesConfig(config: Partial<CoreDatabaseConfiguration>): boolean {
|
||||
const thisDebug = this.config.debug ?? false;
|
||||
const thisCachingStrategy = this.config.cachingStrategy ?? this.DEFAULT_CACHING_STRATEGY;
|
||||
const otherDebug = config.debug ?? false;
|
||||
const otherCachingStrategy = config.cachingStrategy ?? this.DEFAULT_CACHING_STRATEGY;
|
||||
|
||||
return super.matchesConfig(config) && thisDebug === otherDebug && thisCachingStrategy === otherCachingStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
@ -172,24 +179,12 @@ export class CoreDatabaseTableProxy<
|
|||
return this.target.deleteByPrimaryKey(primaryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default configuration values.
|
||||
*
|
||||
* @returns Config defaults.
|
||||
*/
|
||||
protected getConfigDefaults(): CoreDatabaseConfiguration {
|
||||
return {
|
||||
cachingStrategy: CoreDatabaseCachingStrategy.None,
|
||||
debug: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database configuration to use at runtime.
|
||||
*
|
||||
* @returns Database configuration.
|
||||
*/
|
||||
protected async getRuntimeConfig(): Promise<CoreDatabaseConfiguration> {
|
||||
protected async getRuntimeConfig(): Promise<Partial<CoreDatabaseConfiguration>> {
|
||||
await CoreConfig.ready();
|
||||
|
||||
return {
|
||||
|
@ -228,7 +223,8 @@ export class CoreDatabaseTableProxy<
|
|||
const originalTarget = target instanceof CoreDebugDatabaseTable ? target.getTarget() : target;
|
||||
|
||||
return (config.debug && target === originalTarget)
|
||||
|| originalTarget?.constructor !== this.targetConstructors[config.cachingStrategy];
|
||||
|| originalTarget?.constructor !== this.targetConstructors[config.cachingStrategy ?? this.DEFAULT_CACHING_STRATEGY]
|
||||
|| !originalTarget.matchesConfig(config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -238,7 +234,7 @@ export class CoreDatabaseTableProxy<
|
|||
*/
|
||||
protected async createTarget(): Promise<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>> {
|
||||
const config = await this.getRuntimeConfig();
|
||||
const table = this.createTable(config.cachingStrategy);
|
||||
const table = this.createTable(config);
|
||||
|
||||
return config.debug ? new CoreDebugDatabaseTable(table) : table;
|
||||
}
|
||||
|
@ -246,23 +242,29 @@ export class CoreDatabaseTableProxy<
|
|||
/**
|
||||
* Create a database table using the given caching strategy.
|
||||
*
|
||||
* @param cachingStrategy Caching strategy.
|
||||
* @param config Database configuration.
|
||||
* @returns Database table.
|
||||
*/
|
||||
protected createTable(cachingStrategy: CoreDatabaseCachingStrategy): CoreDatabaseTable<DBRecord, PrimaryKeyColumn> {
|
||||
const DatabaseTable = this.targetConstructors[cachingStrategy];
|
||||
protected createTable(config: Partial<CoreDatabaseConfiguration>): CoreDatabaseTable<DBRecord, PrimaryKeyColumn> {
|
||||
const DatabaseTable = this.targetConstructors[config.cachingStrategy ?? this.DEFAULT_CACHING_STRATEGY];
|
||||
|
||||
return new DatabaseTable(this.database, this.tableName, this.primaryKeyColumns);
|
||||
return new DatabaseTable(config, this.database, this.tableName, this.primaryKeyColumns);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Database proxy configuration.
|
||||
*/
|
||||
export interface CoreDatabaseConfiguration {
|
||||
cachingStrategy: CoreDatabaseCachingStrategy;
|
||||
debug: boolean;
|
||||
declare module '@classes/database/database-table' {
|
||||
|
||||
/**
|
||||
* Augment CoreDatabaseConfiguration interface with data specific to this class.
|
||||
*
|
||||
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
|
||||
*/
|
||||
export interface CoreDatabaseConfiguration {
|
||||
cachingStrategy: CoreDatabaseCachingStrategy;
|
||||
debug: boolean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,16 +24,31 @@ export class CoreDatabaseTable<
|
|||
PrimaryKey extends GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> = GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn>
|
||||
> {
|
||||
|
||||
protected config: Partial<CoreDatabaseConfiguration>;
|
||||
protected database: SQLiteDB;
|
||||
protected tableName: string;
|
||||
protected primaryKeyColumns: PrimaryKeyColumn[];
|
||||
protected listeners: CoreDatabaseTableListener[] = [];
|
||||
|
||||
constructor(database: SQLiteDB, tableName: string, primaryKeyColumns?: PrimaryKeyColumn[]) {
|
||||
constructor(
|
||||
config: Partial<CoreDatabaseConfiguration>,
|
||||
database: SQLiteDB,
|
||||
tableName: string,
|
||||
primaryKeyColumns?: PrimaryKeyColumn[],
|
||||
) {
|
||||
this.config = config;
|
||||
this.database = database;
|
||||
this.tableName = tableName;
|
||||
this.primaryKeyColumns = primaryKeyColumns ?? ['id'] as PrimaryKeyColumn[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database configuration.
|
||||
*/
|
||||
getConfig(): Partial<CoreDatabaseConfiguration> {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database connection.
|
||||
*
|
||||
|
@ -72,7 +87,27 @@ export class CoreDatabaseTable<
|
|||
* Destroy.
|
||||
*/
|
||||
async destroy(): Promise<void> {
|
||||
// Nothing to destroy by default, override this method if necessary.
|
||||
this.listeners.forEach(listener => listener.onDestroy?.());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listener.
|
||||
*
|
||||
* @param listener Listener.
|
||||
*/
|
||||
addListener(listener: CoreDatabaseTableListener): void {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the table matches the given configuration for the values that concern it.
|
||||
*
|
||||
* @param config Database config.
|
||||
* @returns Whether the table matches the given configuration.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
matchesConfig(config: Partial<CoreDatabaseConfiguration>): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -336,6 +371,20 @@ export class CoreDatabaseTable<
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Database configuration.
|
||||
*/
|
||||
export interface CoreDatabaseConfiguration {
|
||||
// This definition is augmented in subclasses.
|
||||
}
|
||||
|
||||
/**
|
||||
* Database table listener.
|
||||
*/
|
||||
export interface CoreDatabaseTableListener {
|
||||
onDestroy?(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* CoreDatabaseTable constructor.
|
||||
*/
|
||||
|
@ -346,6 +395,7 @@ export type CoreDatabaseTableConstructor<
|
|||
> = {
|
||||
|
||||
new (
|
||||
config: Partial<CoreDatabaseConfiguration>,
|
||||
database: SQLiteDB,
|
||||
tableName: string,
|
||||
primaryKeyColumns?: PrimaryKeyColumn[]
|
||||
|
|
|
@ -37,7 +37,7 @@ export class CoreDebugDatabaseTable<
|
|||
protected logger: CoreLogger;
|
||||
|
||||
constructor(target: CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey>) {
|
||||
super(target.getDatabase(), target.getTableName(), target.getPrimaryKeyColumns());
|
||||
super(target.getConfig(), target.getDatabase(), target.getTableName(), target.getPrimaryKeyColumns());
|
||||
|
||||
this.target = target;
|
||||
this.logger = CoreLogger.getInstance(`CoreDatabase[${this.tableName}]`);
|
||||
|
@ -53,7 +53,9 @@ export class CoreDebugDatabaseTable<
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
initialize(): Promise<void> {
|
||||
async initialize(): Promise<void> {
|
||||
await super.initialize();
|
||||
|
||||
this.logger.log('initialize', this.target);
|
||||
|
||||
return this.target.initialize();
|
||||
|
@ -62,7 +64,9 @@ export class CoreDebugDatabaseTable<
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
destroy(): Promise<void> {
|
||||
async destroy(): Promise<void> {
|
||||
await super.destroy();
|
||||
|
||||
this.logger.log('destroy');
|
||||
|
||||
return this.target.destroy();
|
||||
|
|
|
@ -40,6 +40,8 @@ export class CoreEagerDatabaseTable<
|
|||
* @inheritdoc
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
await super.initialize();
|
||||
|
||||
const records = await super.getMany();
|
||||
|
||||
this.records = records.reduce((data, record) => {
|
||||
|
|
|
@ -14,7 +14,13 @@
|
|||
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { SQLiteDBRecordValues } from '@classes/sqlitedb';
|
||||
import { CoreDatabaseTable, CoreDatabaseConditions, GetDBRecordPrimaryKey, CoreDatabaseQueryOptions } from './database-table';
|
||||
import {
|
||||
CoreDatabaseConfiguration,
|
||||
CoreDatabaseTable,
|
||||
CoreDatabaseConditions,
|
||||
GetDBRecordPrimaryKey,
|
||||
CoreDatabaseQueryOptions,
|
||||
} from './database-table';
|
||||
|
||||
/**
|
||||
* Wrapper used to improve performance by caching records that are used often for faster read operations.
|
||||
|
@ -28,7 +34,38 @@ export class CoreLazyDatabaseTable<
|
|||
PrimaryKey extends GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> = GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn>
|
||||
> extends CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey> {
|
||||
|
||||
protected readonly DEFAULT_CACHE_LIFETIME = 60000;
|
||||
|
||||
protected records: Record<string, DBRecord | null> = {};
|
||||
protected interval?: number;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
await super.initialize();
|
||||
|
||||
this.interval = window.setInterval(() => (this.records = {}), this.config.lazyCacheLifetime ?? this.DEFAULT_CACHE_LIFETIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async destroy(): Promise<void> {
|
||||
await super.destroy();
|
||||
|
||||
this.interval && window.clearInterval(this.interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
matchesConfig(config: Partial<CoreDatabaseConfiguration>): boolean {
|
||||
const thisCacheLifetime = this.config.lazyCacheLifetime ?? this.DEFAULT_CACHE_LIFETIME;
|
||||
const otherCacheLifetime = config.lazyCacheLifetime ?? this.DEFAULT_CACHE_LIFETIME;
|
||||
|
||||
return super.matchesConfig(config) && thisCacheLifetime === otherCacheLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
|
@ -152,3 +189,16 @@ export class CoreLazyDatabaseTable<
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
declare module '@classes/database/database-table' {
|
||||
|
||||
/**
|
||||
* Augment CoreDatabaseConfiguration interface with data specific to this table.
|
||||
*
|
||||
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
|
||||
*/
|
||||
export interface CoreDatabaseConfiguration {
|
||||
lazyCacheLifetime: number;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,12 +13,8 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { mock, mockSingleton } from '@/testing/utils';
|
||||
import { CoreDatabaseSorting, CoreDatabaseTable } from '@classes/database/database-table';
|
||||
import {
|
||||
CoreDatabaseCachingStrategy,
|
||||
CoreDatabaseConfiguration,
|
||||
CoreDatabaseTableProxy,
|
||||
} from '@classes/database/database-table-proxy';
|
||||
import { CoreDatabaseConfiguration, CoreDatabaseSorting, CoreDatabaseTable } from '@classes/database/database-table';
|
||||
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
|
||||
import { SQLiteDB } from '@classes/sqlitedb';
|
||||
import { CoreConfig } from '@services/config';
|
||||
|
||||
|
|
|
@ -148,6 +148,7 @@ export class CoreCourseProvider {
|
|||
() => CoreSites.getSiteTable(COURSE_STATUS_TABLE, {
|
||||
siteId,
|
||||
config: { cachingStrategy: CoreDatabaseCachingStrategy.Eager },
|
||||
onDestroy: () => delete this.statusTables[siteId],
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ export class CorePushNotificationsProvider {
|
|||
siteId,
|
||||
config: { cachingStrategy: CoreDatabaseCachingStrategy.None },
|
||||
primaryKeyColumns: ['appid', 'uuid'],
|
||||
onDestroy: () => delete this.registeredDevicesTables[siteId],
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -13,14 +13,20 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { CoreConfig, CoreConfigProvider } from '@services/config';
|
||||
import { CoreDB, CoreDbProvider } from '@services/db';
|
||||
import { CoreCustomURLSchemes, CoreCustomURLSchemesProvider } from '@services/urlschemes';
|
||||
import { CoreConstants } from '../constants';
|
||||
|
||||
type DevelopmentWindow = Window & {
|
||||
configProvider?: CoreConfigProvider;
|
||||
dbProvider?: CoreDbProvider;
|
||||
urlSchemes?: CoreCustomURLSchemesProvider;
|
||||
};
|
||||
|
||||
function initializeDevelopmentWindow(window: DevelopmentWindow) {
|
||||
window.configProvider = CoreConfig.instance;
|
||||
window.dbProvider = CoreDB.instance;
|
||||
window.urlSchemes = CoreCustomURLSchemes.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[];
|
||||
}
|
||||
|
|
|
@ -113,6 +113,7 @@ export class CoreFilepoolProvider {
|
|||
siteId,
|
||||
config: { cachingStrategy: CoreDatabaseCachingStrategy.Lazy },
|
||||
primaryKeyColumns: ['fileId'],
|
||||
onDestroy: () => delete this.filesTables[siteId],
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
@ -122,6 +123,7 @@ export class CoreFilepoolProvider {
|
|||
siteId,
|
||||
config: { cachingStrategy: CoreDatabaseCachingStrategy.Lazy },
|
||||
primaryKeyColumns: ['fileId', 'component', 'componentId'],
|
||||
onDestroy: () => delete this.linksTables[siteId],
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
@ -130,6 +132,7 @@ export class CoreFilepoolProvider {
|
|||
() => CoreSites.getSiteTable<CoreFilepoolPackageEntry, 'id'>(PACKAGES_TABLE_NAME, {
|
||||
siteId,
|
||||
config: { cachingStrategy: CoreDatabaseCachingStrategy.Lazy },
|
||||
onDestroy: () => delete this.packagesTables[siteId],
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
@ -149,16 +152,6 @@ export class CoreFilepoolProvider {
|
|||
NgZone.run(() => this.checkQueueProcessing());
|
||||
});
|
||||
});
|
||||
|
||||
CoreEvents.on(CoreEvents.SITE_DELETED, async ({ siteId }) => {
|
||||
if (!siteId || !(siteId in this.filesTables)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.filesTables[siteId].destroy();
|
||||
|
||||
delete this.filesTables[siteId];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -56,12 +56,8 @@ import { CoreAjaxError } from '@classes/errors/ajaxerror';
|
|||
import { CoreAjaxWSError } from '@classes/errors/ajaxwserror';
|
||||
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
|
||||
import { CorePromisedValue } from '@classes/promised-value';
|
||||
import { CoreDatabaseTable } from '@classes/database/database-table';
|
||||
import {
|
||||
CoreDatabaseCachingStrategy,
|
||||
CoreDatabaseConfiguration,
|
||||
CoreDatabaseTableProxy,
|
||||
} from '@classes/database/database-table-proxy';
|
||||
import { CoreDatabaseConfiguration, CoreDatabaseTable } from '@classes/database/database-table';
|
||||
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
|
||||
import { asyncInstance, AsyncInstance } from '../utils/async-instance';
|
||||
import { CoreConfig } from './config';
|
||||
|
||||
|
@ -162,6 +158,7 @@ export class CoreSitesProvider {
|
|||
config: Partial<CoreDatabaseConfiguration>;
|
||||
database: SQLiteDB;
|
||||
primaryKeyColumns: PrimaryKeyColumn[];
|
||||
onDestroy(): void;
|
||||
}> = {},
|
||||
): Promise<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>> {
|
||||
const siteId = options.siteId ?? this.getCurrentSiteId();
|
||||
|
@ -180,6 +177,8 @@ export class CoreSitesProvider {
|
|||
options.primaryKeyColumns,
|
||||
);
|
||||
|
||||
options.onDestroy && table.addListener({ onDestroy: options.onDestroy });
|
||||
|
||||
await table.initialize();
|
||||
|
||||
promisedTable.resolve(table as unknown as CoreDatabaseTable);
|
||||
|
@ -1837,16 +1836,19 @@ export class CoreSitesProvider {
|
|||
* @returns Scehmas Table.
|
||||
*/
|
||||
protected getSiteSchemasTable(site: CoreSite): AsyncInstance<CoreDatabaseTable<SchemaVersionsDBEntry, 'name'>> {
|
||||
this.schemasTables[site.getId()] = this.schemasTables[site.getId()] ?? asyncInstance(
|
||||
const siteId = site.getId();
|
||||
|
||||
this.schemasTables[siteId] = this.schemasTables[siteId] ?? asyncInstance(
|
||||
() => this.getSiteTable(SCHEMA_VERSIONS_TABLE_NAME, {
|
||||
siteId: site.getId(),
|
||||
siteId: siteId,
|
||||
database: site.getDb(),
|
||||
config: { cachingStrategy: CoreDatabaseCachingStrategy.Eager },
|
||||
primaryKeyColumns: ['name'],
|
||||
onDestroy: () => delete this.schemasTables[siteId],
|
||||
}),
|
||||
);
|
||||
|
||||
return this.schemasTables[site.getId()];
|
||||
return this.schemasTables[siteId];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export class CoreObject {
|
|||
* @param b Second object.
|
||||
* @return Whether objects are equal.
|
||||
*/
|
||||
static deepEquals(a: unknown, b: unknown): boolean {
|
||||
static deepEquals<T=unknown>(a: T, b: T): boolean {
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import { CoreMainMenuLocalizedCustomItem } from '@features/mainmenu/services/mai
|
|||
import { CoreSitesDemoSiteData } from '@services/sites';
|
||||
import { OpenFileAction } from '@services/utils/utils';
|
||||
import { CoreLoginSiteSelectorListMethod } from '@features/login/services/login-helper';
|
||||
import { CoreDatabaseConfiguration } from '@classes/database/database-table-proxy';
|
||||
import { CoreDatabaseConfiguration } from '@classes/database/database-table';
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
|
|
Loading…
Reference in New Issue