// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import { CoreError } from '@classes/errors/error'; import { SQLiteDBRecordValues } from '@classes/sqlitedb'; import { CoreDatabaseTable, CoreDatabaseConditions, GetDBRecordPrimaryKey, CoreDatabaseReducer, CoreDatabaseQueryOptions, } from './database-table'; /** * Wrapper used to improve performance by caching all the records for faster read operations. * * This implementation works best for tables that don't have a lot of records and are read often; for tables with too many * records use CoreLazyDatabaseTable instead. */ export class CoreEagerDatabaseTable< DBRecord extends SQLiteDBRecordValues = SQLiteDBRecordValues, PrimaryKeyColumn extends keyof DBRecord = 'id', PrimaryKey extends GetDBRecordPrimaryKey = GetDBRecordPrimaryKey > extends CoreDatabaseTable { protected records: Record = {}; /** * @inheritdoc */ async initialize(): Promise { await super.initialize(); const records = await super.getMany(); this.records = records.reduce((data, record) => { const primaryKey = this.serializePrimaryKey(this.getPrimaryKeyFromRecord(record)); data[primaryKey] = record; return data; }, {}); } /** * @inheritdoc */ async getMany(conditions?: Partial, options?: Partial>): Promise { const records = Object.values(this.records); const filteredRecords = conditions ? records.filter(record => this.recordMatches(record, conditions)) : records; if (options?.sorting) { this.sortRecords(filteredRecords, options.sorting); } return filteredRecords.slice(options?.offset ?? 0, options?.limit); } /** * @inheritdoc */ async getManyWhere(conditions: CoreDatabaseConditions): Promise { return Object.values(this.records).filter(record => conditions.js(record)); } /** * @inheritdoc */ async getOne( conditions?: Partial, options?: Partial, 'offset' | 'limit'>>, ): Promise { let record: DBRecord | undefined; if (options?.sorting) { record = this.getMany(conditions, { ...options, limit: 1 })[0]; } else if (conditions) { record = Object.values(this.records).find(record => this.recordMatches(record, conditions)); } else { record = Object.values(this.records)[0]; } if (!record) { throw new CoreError('No records found.'); } return record; } /** * @inheritdoc */ async getOneByPrimaryKey(primaryKey: PrimaryKey): Promise { const record = this.records[this.serializePrimaryKey(primaryKey)] ?? null; if (record === null) { throw new CoreError('No records found.'); } return record; } /** * @inheritdoc */ async reduce(reducer: CoreDatabaseReducer, conditions?: CoreDatabaseConditions): Promise { return Object .values(this.records) .reduce( (result, record) => (!conditions || conditions.js(record)) ? reducer.js(result, record) : result, reducer.jsInitialValue, ); } /** * @inheritdoc */ async hasAny(conditions?: Partial): Promise { return conditions ? Object.values(this.records).some(record => this.recordMatches(record, conditions)) : Object.values(this.records).length > 0; } /** * @inheritdoc */ async hasAnyByPrimaryKey(primaryKey: PrimaryKey): Promise { return this.serializePrimaryKey(primaryKey) in this.records; } /** * @inheritdoc */ async count(conditions?: Partial): Promise { return conditions ? Object.values(this.records).filter(record => this.recordMatches(record, conditions)).length : Object.values(this.records).length; } /** * @inheritdoc */ async insert(record: DBRecord): Promise { await super.insert(record); const primaryKey = this.serializePrimaryKey(this.getPrimaryKeyFromRecord(record)); this.records[primaryKey] = record; } /** * @inheritdoc */ async update(updates: Partial, conditions?: Partial): Promise { await super.update(updates, conditions); for (const record of Object.values(this.records)) { if (conditions && !this.recordMatches(record, conditions)) { continue; } Object.assign(record, updates); } } /** * @inheritdoc */ async updateWhere(updates: Partial, conditions: CoreDatabaseConditions): Promise { await super.updateWhere(updates, conditions); for (const record of Object.values(this.records)) { if (!conditions.js(record)) { continue; } Object.assign(record, updates); } } /** * @inheritdoc */ async delete(conditions?: Partial): Promise { await super.delete(conditions); if (!conditions) { this.records = {}; return; } Object.entries(this.records).forEach(([id, record]) => { if (!this.recordMatches(record, conditions)) { return; } delete this.records[id]; }); } /** * @inheritdoc */ async deleteByPrimaryKey(primaryKey: PrimaryKey): Promise { await super.deleteByPrimaryKey(primaryKey); delete this.records[this.serializePrimaryKey(primaryKey)]; } }