@athenna/database
Version:
The Athenna database handler for SQL/NoSQL.
1,196 lines (1,195 loc) • 37.6 kB
JavaScript
/**
* @athenna/database
*
* (c) João Lenon <lenon@athenna.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { Is, Json, Exec, Options } from '@athenna/common';
import { debug } from '#src/debug';
import { Log } from '@athenna/logger';
import { ObjectId } from '#src/helpers/ObjectId';
import { Driver } from '#src/database/drivers/Driver';
import { ModelSchema } from '#src/models/schemas/ModelSchema';
import { Transaction } from '#src/database/transactions/Transaction';
import { ConnectionFactory } from '#src/factories/ConnectionFactory';
import { WrongMethodException } from '#src/exceptions/WrongMethodException';
import { MONGO_OPERATIONS_DICTIONARY } from '#src/constants/MongoOperationsDictionary';
import { NotConnectedDatabaseException } from '#src/exceptions/NotConnectedDatabaseException';
import { NotImplementedMethodException } from '#src/exceptions/NotImplementedMethodException';
export class MongoDriver extends Driver {
constructor() {
super(...arguments);
this.primaryKey = '_id';
this.session = null;
/**
* The where clause used in update queries.
*/
this._where = [];
/**
* The or where clause used in update queries.
*/
this._orWhere = [];
/**
* The aggregate pipeline to make mongo queries.
*/
this.pipeline = [];
}
/**
* Set the mongo session that should be used by the driver.
*/
setSession(session) {
this.session = session;
return this;
}
/**
* Connect to database.
*/
connect(options = {}) {
options = Options.create(options, {
force: false,
saveOnFactory: true,
connect: true
});
if (!options.connect) {
return;
}
if (this.isConnected && !options.force) {
return;
}
const mongoose = this.getMongoose();
const configs = Config.get(`database.connections.${this.connection}`, {});
if (configs.debug !== undefined) {
mongoose.set('debug', configs.debug);
}
const mongoOpts = Json.omit(configs, [
'url',
'debug',
'driver',
'validations'
]);
debug('creating new connection using mongoose. options defined: %o', {
url: configs.url,
debug: configs.debug,
...mongoOpts
});
this.client = mongoose.createConnection(configs.url, mongoOpts);
this.client.on('connected', () => {
if (Config.is('rc.bootLogs', true)) {
Log.channelOrVanilla('application').success(`Successfully connected to ({yellow} ${this.connection}) database connection`);
}
});
this.isConnected = true;
this.isSavedOnFactory = options.saveOnFactory;
if (this.isSavedOnFactory) {
ConnectionFactory.setClient(this.connection, this.client);
}
this.qb = this.query();
}
/**
* Close the connection with database in this instance.
*/
async close() {
if (!this.isConnected) {
return;
}
await this.client.close();
this.qb = null;
this.tableName = null;
this.client = null;
this.session = null;
this.isConnected = false;
ConnectionFactory.setClient(this.connection, null);
}
/**
* Creates a new instance of query builder.
*/
query() {
if (!this.isConnected) {
throw new NotConnectedDatabaseException();
}
return this.client.collection(this.tableName || 'lock');
}
/**
* Sync a model schema with database.
*/
async sync(schema) {
const columns = {};
const mongoose = await import('mongoose');
schema.columns.forEach(column => {
columns[column.name] = {};
if (column.type !== undefined) {
columns[column.name].type = column.type;
}
if (column.isNullable !== undefined) {
columns[column.name].required = column.isNullable;
}
if (column.length !== undefined) {
columns[column.name].maxLength = column.length;
}
if (column.defaultTo !== undefined) {
columns[column.name].default = column.defaultTo;
}
if (column.isIndex !== undefined) {
columns[column.name].index = column.isIndex;
}
if (column.isSparse !== undefined) {
columns[column.name].sparse = column.isSparse;
}
if (column.isUnique !== undefined) {
columns[column.name].unique = column.isUnique;
}
if (columns[column.name].unique || columns[column.name].sparse) {
columns[column.name].index = true;
}
});
/**
* Relations will not be registered because
* Athenna will handle them instead of mongoose.
*/
return this.client
.model(schema.getModelName(), new mongoose.Schema(columns))
.createIndexes();
}
/**
* Create a new transaction.
*/
async startTransaction() {
await this.client.asPromise();
const session = await this.client.startSession();
session.startTransaction();
return new Transaction(new MongoDriver(this.connection, this.client).setSession(session));
}
/**
* Commit the transaction.
*/
async commitTransaction() {
await this.client.asPromise();
await this.session.commitTransaction();
await this.session.endSession();
this.tableName = null;
this.client = null;
this.session = null;
this.isConnected = false;
}
/**
* Rollback the transaction.
*/
async rollbackTransaction() {
await this.client.asPromise();
await this.session.abortTransaction();
await this.session.endSession();
this.tableName = null;
this.client = null;
this.session = null;
this.isConnected = false;
}
/**
* Run database migrations.
*/
async runMigrations() {
throw new NotImplementedMethodException(this.runMigrations.name, 'mongo');
}
/**
* Revert database migrations.
*/
async revertMigrations() {
throw new NotImplementedMethodException(this.revertMigrations.name, 'mongo');
}
/**
* List all databases available.
*/
async getDatabases() {
await this.client.asPromise();
const admin = this.client.db.admin();
const { databases } = await admin.listDatabases();
return databases.map(database => database.name);
}
/**
* Get the current database name.
*/
async getCurrentDatabase() {
await this.client.asPromise();
return this.client.db.databaseName;
}
/**
* Verify if database exists.
*/
async hasDatabase(database) {
await this.client.asPromise();
const databases = await this.getDatabases();
return databases.includes(database);
}
/**
* Create a new database.
*/
async createDatabase() {
throw new NotImplementedMethodException(this.createDatabase.name, 'mongo');
}
/**
* Drop some database.
*/
async dropDatabase(database) {
await this.client.asPromise();
await this.client.useDb(database).dropDatabase();
}
/**
* List all tables available.
*/
async getTables() {
await this.client.asPromise();
const collections = await this.client.db.listCollections().toArray();
return collections.map(collection => collection.name);
}
/**
* Verify if table exists.
*/
async hasTable(table) {
await this.client.asPromise();
const tables = await this.getTables();
return tables.includes(table);
}
/**
* Create a new table in database.
*/
async createTable() {
throw new NotImplementedMethodException(this.createTable.name, 'mongo');
}
/**
* Drop a table in database.
*/
async dropTable(table) {
try {
await this.client.asPromise();
await this.client.dropCollection(table);
}
catch (err) {
debug('error happened while dropping table %s in MongoDriver: %o', err);
}
}
/**
* Remove all data inside some database table
* and restart the identity of the table.
*/
async truncate(table) {
await this.client.asPromise();
const collection = this.client.collection(table);
await collection.deleteMany({}, { session: this.session });
}
/**
* Make a raw query in database.
*/
raw() {
throw new NotImplementedMethodException(this.raw.name, 'mongo');
}
/**
* Calculate the average of a given column.
*/
async avg(column) {
await this.client.asPromise();
const pipeline = this.createPipeline();
pipeline.push({
$group: { [this.primaryKey]: null, avg: { $avg: `$${column}` } }
});
pipeline.push({ $project: { [this.primaryKey]: 0, avg: 1 } });
const result = await this.qb
.aggregate(pipeline, { session: this.session })
.toArray();
if (Is.Empty(result)) {
return null;
}
return `${result[0].avg}`;
}
/**
* Calculate the average of a given column using distinct.
*/
async avgDistinct(column) {
await this.client.asPromise();
const pipeline = this.createPipeline();
pipeline.push({
$group: { [this.primaryKey]: null, set: { $addToSet: `$${column}` } }
});
pipeline.push({ $project: { [this.primaryKey]: 0, avg: { $avg: '$set' } } });
const result = await this.qb
.aggregate(pipeline, { session: this.session })
.toArray();
if (Is.Empty(result)) {
return null;
}
return `${result[0].avg}`;
}
/**
* Get the max number of a given column.
*/
async max(column) {
await this.client.asPromise();
const pipeline = this.createPipeline();
pipeline.push({
$group: { [this.primaryKey]: null, max: { $max: `$${column}` } }
});
pipeline.push({ $project: { [this.primaryKey]: 0, max: 1 } });
const result = await this.qb
.aggregate(pipeline, { session: this.session })
.toArray();
if (Is.Empty(result)) {
return null;
}
return `${result[0].max}`;
}
/**
* Get the min number of a given column.
*/
async min(column) {
await this.client.asPromise();
const pipeline = this.createPipeline();
pipeline.push({
$group: { [this.primaryKey]: null, min: { $min: `$${column}` } }
});
pipeline.push({ $project: { [this.primaryKey]: 0, min: 1 } });
const result = await this.qb
.aggregate(pipeline, { session: this.session })
.toArray();
if (Is.Empty(result)) {
return null;
}
return `${result[0].min}`;
}
/**
* Sum all numbers of a given column.
*/
async sum(column) {
await this.client.asPromise();
const pipeline = this.createPipeline();
pipeline.push({
$group: { [this.primaryKey]: null, sum: { $sum: `$${column}` } }
});
pipeline.push({ $project: { [this.primaryKey]: 0, sum: 1 } });
const result = await this.qb
.aggregate(pipeline, { session: this.session })
.toArray();
if (Is.Empty(result)) {
return null;
}
return `${result[0].sum}`;
}
/**
* Sum all numbers of a given column in distinct mode.
*/
async sumDistinct(column) {
await this.client.asPromise();
const pipeline = this.createPipeline();
pipeline.push({
$group: { [this.primaryKey]: null, set: { $addToSet: `$${column}` } }
});
pipeline.push({ $project: { [this.primaryKey]: 0, sum: { $sum: '$set' } } });
const result = await this.qb
.aggregate(pipeline, { session: this.session })
.toArray();
if (Is.Empty(result)) {
return null;
}
return `${result[0].sum}`;
}
/**
* Increment a value of a given column.
*/
async increment(column) {
await this.client.asPromise();
const where = this.createWhere();
await this.qb.updateMany(where, { $inc: { [column]: 1 } }, { session: this.session, upsert: false });
}
/**
* Decrement a value of a given column.
*/
async decrement(column) {
await this.client.asPromise();
const where = this.createWhere();
await this.qb.updateMany(where, { $inc: { [column]: -1 } }, { session: this.session, upsert: false });
}
/**
* Calculate the average of a given column using distinct.
*/
async count(column = '*') {
await this.client.asPromise();
const pipeline = this.createPipeline();
if (column !== '*') {
pipeline.push({ $match: { [column]: { $ne: null } } });
}
pipeline.push({ $group: { [this.primaryKey]: null, count: { $sum: 1 } } });
pipeline.push({ $project: { [this.primaryKey]: 0, count: 1 } });
const result = await this.qb
.aggregate(pipeline, { session: this.session })
.toArray();
return `${result[0]?.count || 0}`;
}
/**
* Calculate the average of a given column using distinct.
*/
async countDistinct(column) {
await this.client.asPromise();
const pipeline = this.createPipeline();
if (column !== '*') {
pipeline.push({ $match: { [column]: { $ne: null } } });
}
pipeline.push({
$group: { [this.primaryKey]: null, set: { $addToSet: `$${column}` } }
});
pipeline.push({
$project: { [this.primaryKey]: 0, count: { $size: `$set` } }
});
const [{ count }] = await this.qb
.aggregate(pipeline, { session: this.session })
.toArray();
return `${count}`;
}
/**
* Find a value in database.
*/
async find() {
await this.client.asPromise();
const pipeline = this.createPipeline();
const data = await this.qb
.aggregate(pipeline, { session: this.session })
.toArray();
return data[0];
}
/**
* Find many values in database.
*/
async findMany() {
await this.client.asPromise();
const pipeline = this.createPipeline();
return this.qb
.aggregate(pipeline, { session: this.session })
.toArray();
}
/**
* Find many values in database and return as paginated response.
*/
async paginate(page = { page: 0, limit: 10, resourceUrl: '/' }, limit = 10, resourceUrl = '/') {
await this.client.asPromise();
if (Is.Number(page)) {
page = { page, limit, resourceUrl };
}
const pipeline = this.createPipeline({
clearWhere: false,
clearOrWhere: false,
clearPipeline: false
});
pipeline.push({ $group: { [this.primaryKey]: null, count: { $sum: 1 } } });
pipeline.push({ $project: { [this.primaryKey]: 0, count: 1 } });
const result = await this.qb
.aggregate(pipeline, { session: this.session })
.toArray();
const count = result[0]?.count || 0;
const data = await this.offset(page.page).limit(page.limit).findMany();
return Exec.pagination(data, count, page);
}
/**
* Create a value in database.
*/
async create(data = {}) {
if (Is.Array(data)) {
throw new WrongMethodException('create', 'createMany');
}
await this.client.asPromise();
const created = await this.createMany([data]);
return created[0];
}
/**
* Create many values in database.
*/
async createMany(data = []) {
if (!Is.Array(data)) {
throw new WrongMethodException('createMany', 'create');
}
await this.client.asPromise();
const { insertedIds } = await this.qb.insertMany(data, {
session: this.session
});
const insertedIdsArray = [];
Object.keys(insertedIds).forEach(key => insertedIdsArray.push(insertedIds[key]));
return this.whereIn(this.primaryKey, insertedIdsArray).findMany();
}
/**
* Create data or update if already exists.
*/
async createOrUpdate(data = {}) {
await this.client.asPromise();
const pipeline = this.createPipeline();
const hasValue = (await this.qb.aggregate(pipeline, { session: this.session }).toArray())[0];
if (hasValue) {
return this.where(this.primaryKey, hasValue[this.primaryKey]).update(data);
}
return this.create(data);
}
/**
* Update a value in database.
*/
async update(data) {
await this.client.asPromise();
const where = this.createWhere({ clearWhere: false, clearOrWhere: false });
const pipeline = this.createPipeline();
await this.qb.updateMany(where, { $set: data }, { upsert: false, session: this.session });
const result = await this.qb
.aggregate(pipeline, { session: this.session })
.toArray();
if (result.length === 1) {
return result[0];
}
return result;
}
/**
* Delete one value in database.
*/
async delete() {
await this.client.asPromise();
await this.qb.deleteMany(this.createWhere(), { session: this.session });
}
/**
* Set the table that this query will be executed.
*/
table(table) {
if (!this.isConnected) {
throw new NotConnectedDatabaseException();
}
this.tableName = table;
this.qb = this.query();
return this;
}
/**
* Log in console the actual query built.
*/
dump() {
console.log({
where: this._where,
orWhere: this._orWhere,
pipeline: this.pipeline
});
return this;
}
/**
* Set the columns that should be selected on query.
*/
select(...columns) {
if (columns.includes('*')) {
return this;
}
if (!columns.includes('_id')) {
const isAlreadyHide = !!this.pipeline
.map(step => {
if (!step.$project) {
return false;
}
if (!step.$project._id) {
return false;
}
if (step.$project._id === 0) {
return false;
}
return true;
})
.find(value => value === true);
if (!isAlreadyHide) {
this.pipeline.push({ $project: { _id: 0 } });
}
}
const $project = columns.reduce((previous, column) => {
if (column.includes(`${this.tableName}.`)) {
column = column.replace(`${this.tableName}.`, '');
}
if (column.includes(' as ')) {
const [select, alias] = column.split(' as ');
previous[select] = 0;
previous[alias] = `$${select}`;
return previous;
}
previous[column] = 1;
return previous;
}, {});
this.pipeline.push({ $project });
return this;
}
/**
* Set the columns that should be selected on query raw.
*/
selectRaw() {
throw new NotImplementedMethodException(this.selectRaw.name, 'mongo');
}
/**
* Set the table that should be used on query.
* Different from `table()` method, this method
* doesn't change the driver table.
*/
from() {
throw new NotImplementedMethodException(this.from.name, 'mongo');
}
/**
* Set the table that should be used on query raw.
* Different from `table()` method, this method
* doesn't change the driver table.
*/
fromRaw() {
throw new NotImplementedMethodException(this.selectRaw.name, 'mongo');
}
/**
* Set a join statement in your query.
*/
join(table, column1, operation, column2) {
let foreignField = column2 || operation || this.primaryKey;
if (foreignField.includes('.')) {
foreignField = foreignField.split('.')[1];
}
let localField = column1 || this.primaryKey;
if (localField.includes('.')) {
localField = localField.split('.')[1];
}
this.pipeline.push({
$lookup: { from: table, localField, foreignField, as: table }
});
return this;
}
/**
* Set a left join statement in your query.
*/
leftJoin(table, column1, operation, column2) {
return this.join(table, column1, operation, column2);
}
/**
* Set a right join statement in your query.
*/
rightJoin(table, column1, operation, column2) {
return this.join(table, column1, operation, column2);
}
/**
* Set a cross join statement in your query.
*/
crossJoin(table, column1, operation, column2) {
return this.join(table, column1, operation, column2);
}
/**
* Set a full outer join statement in your query.
*/
fullOuterJoin(table, column1, operation, column2) {
return this.join(table, column1, operation, column2);
}
/**
* Set a left outer join statement in your query.
*/
leftOuterJoin(table, column1, operation, column2) {
return this.join(table, column1, operation, column2);
}
/**
* Set a right outer join statement in your query.
*/
rightOuterJoin(table, column1, operation, column2) {
return this.join(table, column1, operation, column2);
}
/**
* Set a join raw statement in your query.
*/
joinRaw() {
throw new NotImplementedMethodException(this.joinRaw.name, 'mongo');
}
/**
* Set a group by statement in your query.
*/
groupBy(...columns) {
const $group = { [this.primaryKey]: {} };
columns.forEach(column => ($group[this.primaryKey][column] = `$${column}`));
this.pipeline.push({ $group });
this.pipeline.push({ $replaceRoot: { newRoot: `$${this.primaryKey}` } });
return this;
}
/**
* Set a group by raw statement in your query.
*/
groupByRaw() {
throw new NotImplementedMethodException(this.groupByRaw.name, 'mongo');
}
/**
* Set a having statement in your query.
*/
having(column, operation, value) {
return this.where(column, operation, value);
}
/**
* Set a having raw statement in your query.
*/
havingRaw() {
throw new NotImplementedMethodException(this.havingRaw.name, 'mongo');
}
/**
* Set a having exists statement in your query.
*/
havingExists() {
throw new NotImplementedMethodException(this.havingExists.name, 'mongo');
}
/**
* Set a having not exists statement in your query.
*/
havingNotExists() {
throw new NotImplementedMethodException(this.havingNotExists.name, 'mongo');
}
/**
* Set a having in statement in your query.
*/
havingIn(column, values) {
return this.whereIn(column, values);
}
/**
* Set a having not in statement in your query.
*/
havingNotIn(column, values) {
return this.whereNotIn(column, values);
}
/**
* Set a having between statement in your query.
*/
havingBetween(column, values) {
return this.whereBetween(column, values);
}
/**
* Set a having not between statement in your query.
*/
havingNotBetween(column, values) {
return this.whereNotBetween(column, values);
}
/**
* Set a having null statement in your query.
*/
havingNull(column) {
return this.whereNull(column);
}
/**
* Set a having not null statement in your query.
*/
havingNotNull(column) {
return this.whereNotNull(column);
}
/**
* Set an or having statement in your query.
*/
orHaving(column, operation, value) {
return this.orWhere(column, operation, value);
}
/**
* Set an or having raw statement in your query.
*/
orHavingRaw() {
throw new NotImplementedMethodException(this.orHavingRaw.name, 'mongo');
}
/**
* Set an or having exists statement in your query.
*/
orHavingExists() {
throw new NotImplementedMethodException(this.orHavingExists.name, 'mongo');
}
/**
* Set an or having not exists statement in your query.
*/
orHavingNotExists() {
throw new NotImplementedMethodException(this.orHavingNotExists.name, 'mongo');
}
/**
* Set an or having in statement in your query.
*/
orHavingIn(column, values) {
return this.orWhereIn(column, values);
}
/**
* Set an or having not in statement in your query.
*/
orHavingNotIn(column, values) {
return this.orWhereNotIn(column, values);
}
/**
* Set an or having between statement in your query.
*/
orHavingBetween(column, values) {
return this.orWhereBetween(column, values);
}
/**
* Set an or having not between statement in your query.
*/
orHavingNotBetween(column, values) {
return this.orWhereNotBetween(column, values);
}
/**
* Set an or having null statement in your query.
*/
orHavingNull(column) {
return this.whereNull(column);
}
/**
* Set an or having not null statement in your query.
*/
orHavingNotNull(column) {
return this.whereNotNull(column);
}
/**
* Set a where statement in your query.
*/
where(statement, operation, value) {
if (Is.Function(statement)) {
statement(this);
return this;
}
if (operation === undefined) {
this._where.push(statement);
return this;
}
if (value === undefined) {
this._where.push({
[statement]: this.setOperator(operation, '=')
});
return this;
}
this._where.push({ [statement]: this.setOperator(value, operation) });
return this;
}
/**
* Set a where not statement in your query.
*/
whereNot(statement, value) {
return this.where(statement, '<>', value);
}
/**
* Set a where raw statement in your query.
*/
whereRaw() {
throw new NotImplementedMethodException(this.whereRaw.name, 'mongo');
}
/**
* Set a where exists statement in your query.
*/
whereExists() {
throw new NotImplementedMethodException(this.whereExists.name, 'mongo');
}
/**
* Set a where not exists statement in your query.
*/
whereNotExists() {
throw new NotImplementedMethodException(this.whereNotExists.name, 'mongo');
}
/**
* Set a where like statement in your query.
*/
whereLike(column, value) {
return this.where(column, 'like', value);
}
/**
* Set a where ILike statement in your query.
*/
whereILike(column, value) {
return this.where(column, 'ilike', value);
}
/**
* Set a where in statement in your query.
*/
whereIn(column, values) {
values = values.flatMap(value => {
if (ObjectId.isValidStringOrObject(value)) {
return [String(value), new ObjectId(value)];
}
return [value];
});
this._where.push({ [column]: { $in: values } });
return this;
}
/**
* Set a where not in statement in your query.
*/
whereNotIn(column, values) {
values = values.flatMap(value => {
if (ObjectId.isValidStringOrObject(value)) {
return [String(value), new ObjectId(value)];
}
return [value];
});
this._where.push({ [column]: { $nin: values } });
return this;
}
/**
* Set a where between statement in your query.
*/
whereBetween(column, values) {
this._where.push({ [column]: { $gte: values[0], $lte: values[1] } });
return this;
}
/**
* Set a where not between statement in your query.
*/
whereNotBetween(column, values) {
this._where.push({
[column]: { $not: { $gte: values[0], $lte: values[1] } }
});
return this;
}
/**
* Set a where null statement in your query.
*/
whereNull(column) {
this._where.push({ [column]: null });
return this;
}
/**
* Set a where not null statement in your query.
*/
whereNotNull(column) {
this._where.push({ [column]: { $ne: null } });
return this;
}
/**
* Set a or where statement in your query.
*/
orWhere(statement, operation, value) {
if (Is.Function(statement)) {
statement(this);
return this;
}
if (operation === undefined) {
this._orWhere.push(statement);
return this;
}
if (value === undefined) {
this._orWhere.push({ [statement]: this.setOperator(operation, '=') });
return this;
}
this._orWhere.push({ [statement]: this.setOperator(value, operation) });
return this;
}
/**
* Set an or where not statement in your query.
*/
orWhereNot(statement, value) {
return this.orWhere(statement, '<>', value);
}
/**
* Set a or where raw statement in your query.
*/
orWhereRaw() {
throw new NotImplementedMethodException(this.orWhereRaw.name, 'mongo');
}
/**
* Set an or where exists statement in your query.
*/
orWhereExists() {
throw new NotImplementedMethodException(this.orWhereExists.name, 'mongo');
}
/**
* Set an or where not exists statement in your query.
*/
orWhereNotExists() {
throw new NotImplementedMethodException(this.orWhereNotExists.name, 'mongo');
}
/**
* Set an or where like statement in your query.
*/
orWhereLike(column, value) {
return this.orWhere(column, 'like', value);
}
/**
* Set an or where ILike statement in your query.
*/
orWhereILike(column, value) {
return this.orWhere(column, 'ilike', value);
}
/**
* Set an or where in statement in your query.
*/
orWhereIn(column, values) {
values = values.flatMap(value => {
if (ObjectId.isValidStringOrObject(value)) {
return [String(value), new ObjectId(value)];
}
return [value];
});
this._orWhere.push({ [column]: { $in: values } });
return this;
}
/**
* Set an or where not in statement in your query.
*/
orWhereNotIn(column, values) {
values = values.flatMap(value => {
if (ObjectId.isValidStringOrObject(value)) {
return [String(value), new ObjectId(value)];
}
return [value];
});
this._orWhere.push({ [column]: { $nin: values } });
return this;
}
/**
* Set an or where between statement in your query.
*/
orWhereBetween(column, values) {
this._orWhere.push({ [column]: { $gte: values[0], $lte: values[1] } });
return this;
}
/**
* Set an or where not between statement in your query.
*/
orWhereNotBetween(column, values) {
this._orWhere.push({
[column]: { $not: { $gte: values[0], $lte: values[1] } }
});
return this;
}
/**
* Set an or where null statement in your query.
*/
orWhereNull(column) {
this._orWhere.push({ [column]: null });
return this;
}
/**
* Set an or where not null statement in your query.
*/
orWhereNotNull(column) {
this._orWhere.push({ [column]: { $ne: null } });
return this;
}
/**
* Set an order by statement in your query.
*/
orderBy(column, direction = 'ASC') {
this.pipeline.push({
$sort: { [column]: direction.toLowerCase() === 'asc' ? 1 : -1 }
});
return this;
}
/**
* Set an order by raw statement in your query.
*/
orderByRaw() {
throw new NotImplementedMethodException(this.orderByRaw.name, 'mongo');
}
/**
* Order the results easily by the latest date. By default, the result will
* be ordered by the table's "createdAt" column.
*/
latest(column = 'createdAt') {
return this.orderBy(column, 'DESC');
}
/**
* Order the results easily by the oldest date. By default, the result will
* be ordered by the table's "createdAt" column.
*/
oldest(column = 'createdAt') {
return this.orderBy(column, 'ASC');
}
/**
* Set the skip number in your query.
*/
offset(number) {
this.pipeline.push({ $skip: number });
return this;
}
/**
* Set the limit number in your query.
*/
limit(number) {
this.pipeline.push({ $limit: number });
return this;
}
/**
* Set the mongo operation in value.
*/
setOperator(value, operator) {
if (operator === '=') {
return value;
}
const mongoOperator = MONGO_OPERATIONS_DICTIONARY[operator];
const object = { [mongoOperator]: value };
if (operator === 'like' || operator === 'ilike') {
let valueRegexString = value.replace(/%/g, '');
if (!value.startsWith('%') && value.endsWith('%')) {
valueRegexString = `^${valueRegexString}`;
}
else if (value.startsWith('%') && !value.endsWith('%')) {
valueRegexString = `${valueRegexString}$`;
}
object[mongoOperator] = new RegExp(valueRegexString);
}
if (operator === 'ilike') {
object.$options = 'i';
}
return object;
}
/**
* Creates the where clause with where and orWhere.
*/
createWhere(options = {}) {
options = Options.create(options, {
clearWhere: true,
clearOrWhere: true
});
const where = {};
if (!Is.Empty(this._where)) {
where.$and = Json.copy(this._where).map(condition => {
const keysToSwap = Object.keys(condition).filter(key => {
const value = condition[key];
if (ObjectId.isValidStringOrObject(value)) {
return true;
}
return false;
});
keysToSwap.forEach(key => {
if (!condition.$or) {
condition.$or = [];
}
const objectId = condition[key];
condition.$or.push({ [key]: String(objectId) }, { [key]: new ObjectId(objectId) });
delete condition[key];
});
return condition;
});
}
if (!Is.Empty(this._orWhere)) {
where.$or = Json.copy(this._orWhere).map(condition => {
const keysToSwap = Object.keys(condition).filter(key => {
const value = condition[key];
if (ObjectId.isValidStringOrObject(value)) {
return true;
}
return false;
});
keysToSwap.forEach(key => {
if (!condition.$or) {
condition.$or = [];
}
const objectId = condition[key];
condition.$or.push({ [key]: String(objectId) }, { [key]: new ObjectId(objectId) });
delete condition[key];
});
return condition;
});
}
if (options.clearWhere) {
this._where = [];
}
if (options.clearOrWhere) {
this._orWhere = [];
}
return where;
}
/**
* Creates the aggregation pipeline.
*/
createPipeline(options = {}) {
options = Options.create(options, {
clearWhere: true,
clearOrWhere: true,
clearPipeline: true
});
const pipeline = Json.copy(this.pipeline);
if (options.clearPipeline) {
this.pipeline = [];
}
pipeline.push({ $match: this.createWhere(options) });
return pipeline;
}
}