@athenna/database
Version:
The Athenna database handler for SQL/NoSQL.
1,285 lines (1,284 loc) • 40.6 kB
JavaScript
/* eslint-disable @typescript-eslint/ban-ts-comment */
/**
* @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 { Exec, Is, Json, Options } from '@athenna/common';
import { debug } from '#src/debug';
import { Log } from '@athenna/logger';
import { Driver } from '#src/database/drivers/Driver';
import { ConnectionFactory } from '#src/factories/ConnectionFactory';
import { Transaction } from '#src/database/transactions/Transaction';
import { MigrationSource } from '#src/database/migrations/MigrationSource';
import { EmptyValueException } from '#src/exceptions/EmptyValueException';
import { EmptyColumnException } from '#src/exceptions/EmptyColumnException';
import { WrongMethodException } from '#src/exceptions/WrongMethodException';
import { PROTECTED_QUERY_METHODS } from '#src/constants/ProtectedQueryMethods';
import { NotConnectedDatabaseException } from '#src/exceptions/NotConnectedDatabaseException';
export class BaseKnexDriver extends Driver {
/**
* 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 knex = this.getKnex();
const configs = Config.get(`database.connections.${this.connection}`, {});
const knexOpts = {
migrations: {
tableName: 'migrations'
},
pool: {
min: 2,
max: 20,
acquireTimeoutMillis: 60 * 1000
},
debug: false,
useNullAsDefault: false,
...Json.omit(configs, ['driver', 'validations'])
};
debug('creating new connection using Knex. options defined: %o', knexOpts);
if (Config.is('rc.bootLogs', true)) {
Log.channelOrVanilla('application').success(`Successfully connected to ({yellow} ${this.connection}) database connection`);
}
this.client = knex.default(knexOpts);
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.destroy();
this.qb = null;
this.tableName = null;
this.client = null;
this.isConnected = false;
ConnectionFactory.setClient(this.connection, null);
}
/**
* Creates a new instance of query builder.
*/
query() {
if (!this.isConnected) {
throw new NotConnectedDatabaseException();
}
const query = this.useSetQB
? this.qb.table(this.tableName)
: this.client.queryBuilder().table(this.tableName);
const handler = {
get: (target, propertyKey) => {
if (PROTECTED_QUERY_METHODS.includes(propertyKey)) {
this.qb = this.query();
}
return target[propertyKey];
}
};
return new Proxy(query, handler);
}
/**
* Sync a model schema with database.
*/
async sync() {
debug(`database sync with ${this.constructor.name} is not available yet, use migration instead.`);
}
/**
* Create a new transaction.
*/
async startTransaction() {
const trx = await this.client.transaction();
return new Transaction(this.clone().setClient(trx));
}
/**
* Commit the transaction.
*/
async commitTransaction() {
const client = this.client;
await client.commit();
this.tableName = null;
this.client = null;
this.isConnected = false;
}
/**
* Rollback the transaction.
*/
async rollbackTransaction() {
const client = this.client;
await client.rollback();
this.tableName = null;
this.client = null;
this.isConnected = false;
}
/**
* Run database migrations.
*/
async runMigrations() {
await this.client.migrate.latest({
migrationSource: new MigrationSource(this.connection)
});
}
/**
* Revert database migrations.
*/
async revertMigrations() {
await this.client.migrate.rollback({
migrationSource: new MigrationSource(this.connection)
});
}
/**
* List all databases available.
*/
async getDatabases() {
const [databases] = await this.raw('SHOW DATABASES');
return databases.map(database => database.Database);
}
/**
* Get the current database name.
*/
async getCurrentDatabase() {
return this.client.client.database();
}
/**
* Verify if database exists.
*/
async hasDatabase(database) {
const databases = await this.getDatabases();
return databases.includes(database);
}
/**
* Create a new database.
*/
async createDatabase(database) {
await this.raw('CREATE DATABASE IF NOT EXISTS ??', database);
}
/**
* Drop some database.
*/
async dropDatabase(database) {
await this.raw('DROP DATABASE IF EXISTS ??', database);
}
/**
* List all tables available.
*/
async getTables() {
const [tables] = await this.raw('SELECT table_name FROM information_schema.tables WHERE table_schema = ?', await this.getCurrentDatabase());
return tables.map(table => table.TABLE_NAME);
}
/**
* Verify if table exists.
*/
async hasTable(table) {
return this.client.schema.hasTable(table);
}
/**
* Create a new table in database.
*/
async createTable(table, closure) {
await this.client.schema.createTable(table, closure);
}
/**
* Alter a table in database.
*/
async alterTable(table, closure) {
await this.client.schema.alterTable(table, closure);
}
/**
* Drop a table in database.
*/
async dropTable(table) {
await this.client.schema.dropTableIfExists(table);
}
/**
* Remove all data inside some database table
* and restart the identity of the table.
*/
async truncate(table) {
try {
await this.raw('SET FOREIGN_KEY_CHECKS = 0');
await this.raw('TRUNCATE TABLE ??', table);
}
finally {
await this.raw('SET FOREIGN_KEY_CHECKS = 1');
}
}
/**
* Make a raw query in database.
*/
raw(sql, bindings) {
return this.client.raw(sql, bindings);
}
/**
* Calculate the average of a given column.
*/
async avg(column) {
const [{ avg }] = await this.qb.clearSelect().avg({ avg: column });
return avg == null ? null : Number(avg);
}
/**
* Calculate the average of a given column using distinct.
*/
async avgDistinct(column) {
const [{ avg }] = await this.qb.clearSelect().avgDistinct({ avg: column });
return avg == null ? null : Number(avg);
}
/**
* Get the max number of a given column.
*/
async max(column) {
const [{ max }] = await this.qb.clearSelect().max({ max: column });
return max == null ? null : Number(max);
}
/**
* Get the min number of a given column.
*/
async min(column) {
const [{ min }] = await this.qb.clearSelect().min({ min: column });
return min == null ? null : Number(min);
}
/**
* Sum all numbers of a given column.
*/
async sum(column) {
const [{ sum }] = await this.qb.clearSelect().sum({ sum: column });
return sum == null ? null : Number(sum);
}
/**
* Sum all numbers of a given column in distinct mode.
*/
async sumDistinct(column) {
const [{ sum }] = await this.qb.clearSelect().sumDistinct({ sum: column });
return sum == null ? null : Number(sum);
}
/**
* Increment a value of a given column.
*/
async increment(column) {
await this.qb.increment(column);
}
/**
* Decrement a value of a given column.
*/
async decrement(column) {
await this.qb.decrement(column);
}
/**
* Calculate the average of a given column using distinct.
*/
async count(column = '*') {
const [{ count }] = await this.qb.clearSelect().count({ count: column });
return Number(count);
}
/**
* Calculate the average of a given column using distinct.
*/
async countDistinct(column) {
const [{ count }] = await this.qb
.clearSelect()
.countDistinct({ count: column });
return Number(count);
}
/**
* Find a value in database.
*/
async find() {
return this.qb.first();
}
/**
* Find many values in database.
*/
async findMany() {
const data = await this.qb;
this.qb = this.query();
return data;
}
/**
* Find many values in database and return as paginated response.
*/
async paginate(page = { page: 0, limit: 10, resourceUrl: '/' }, limit = 10, resourceUrl = '/') {
if (Is.Number(page)) {
page = { page, limit, resourceUrl };
}
const [{ count }] = await this.qb
.clone()
.clearOrder()
.clearSelect()
.count({ count: '*' });
const data = await this.offset(page.page * page.limit)
.limit(page.limit)
.findMany();
return Exec.pagination(data, Number(count), page);
}
/**
* Create a value in database.
*/
async create(data = {}) {
if (Is.Array(data)) {
throw new WrongMethodException('create', 'createMany');
}
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');
}
const preparedData = data.map(data => this.prepareInsert(data));
const ids = [];
const promises = preparedData.map((prepared, index) => {
return this.qb
.clone()
.insert(prepared)
.then(([id]) => ids.push(data[index][this.primaryKey] || id));
});
await Promise.all(promises);
return this.whereIn(this.primaryKey, ids).findMany();
}
/**
* Create data or update if already exists.
*/
async createOrUpdate(data) {
const query = this.qb.clone();
const hasValue = await query.first();
const preparedData = this.prepareInsert(data);
if (hasValue) {
await this.qb
.where(this.primaryKey, hasValue[this.primaryKey])
.limit(1)
.update(preparedData);
return this.where(this.primaryKey, hasValue[this.primaryKey]).find();
}
return this.create(data);
}
/**
* Update a value in database.
*/
async update(data) {
const preparedData = this.prepareInsert(data);
await this.qb.clone().update(preparedData);
const result = await this.findMany();
if (result.length === 1) {
return result[0];
}
return result;
}
/**
* Stringify object-like values before persisting with Knex.
*/
prepareInsert(data) {
return Object.entries(data).reduce((prepared, [key, value]) => {
if (!this.shouldStringifyJsonValue(value)) {
prepared[key] = value;
return prepared;
}
prepared[key] = JSON.stringify(value);
return prepared;
}, {});
}
/**
* Verify if a value should be serialized before persisting.
*
* Knex `Raw` instances must NEVER be stringified: they hold an internal
* reference to the database client (and its connection pool, which contains
* Node.js `Timeout` objects with circular references), so calling
* `JSON.stringify` on them throws `TypeError: Converting circular structure
* to JSON`. Knex marks every `Raw` instance with `isRawInstance = true` and
* relies on it internally during query compilation, so we trust the same
* marker here. The same precaution applies to Knex `QueryBuilder` instances
* (subqueries) which expose `isQueryBuilder = true`.
*/
shouldStringifyJsonValue(value) {
if (!value) {
return false;
}
if (value.isRawInstance || value.isQueryBuilder) {
return false;
}
return Is.Array(value) || Is.Object(value);
}
/**
* Delete one value in database.
*/
async delete() {
await this.qb.delete();
}
/**
* Set the table that this query will be executed.
*/
table(table) {
if (!this.isConnected) {
throw new NotConnectedDatabaseException();
}
if (!Is.String(table)) {
throw new Error('Table must be a string value');
}
this.tableName = table;
this.qb = this.query();
return this;
}
/**
* Log in console the actual query built.
*/
dump() {
process.stdout.write(`${JSON.stringify(this.qb.toSQL().toNative())}\n`);
return this;
}
/**
* Set the columns that should be selected on query.
*/
select(...columns) {
if (!Is.Array(columns)) {
throw new EmptyValueException('select');
}
this.qb.select(...columns);
return this;
}
/**
* Set the columns that should be selected on query raw.
*/
selectRaw(sql, bindings) {
if (Is.Undefined(sql)) {
throw new EmptyValueException('selectRaw');
}
return this.select(this.raw(sql, bindings));
}
/**
* Set the table that should be used on query.
* Different from `table()` method, this method
* doesn't change the driver table.
*/
from(table) {
if (Is.Undefined(table)) {
throw new Error('Table must be a string value');
}
this.qb.from(table);
return this;
}
/**
* Set the table that should be used on query raw.
* Different from `table()` method, this method
* doesn't change the driver table.
*/
fromRaw(sql, bindings) {
if (Is.Undefined(sql)) {
throw new EmptyValueException('fromRaw');
}
return this.from(this.raw(sql, bindings));
}
/**
* Set a join statement in your query.
*/
join(table, column1, operation, column2) {
return this.joinByType('join', table, column1, operation, column2);
}
/**
* Set a left join statement in your query.
*/
leftJoin(table, column1, operation, column2) {
return this.joinByType('leftJoin', table, column1, operation, column2);
}
/**
* Set a right join statement in your query.
*/
rightJoin(table, column1, operation, column2) {
return this.joinByType('rightJoin', table, column1, operation, column2);
}
/**
* Set a cross join statement in your query.
*/
crossJoin(table, column1, operation, column2) {
return this.joinByType('crossJoin', table, column1, operation, column2);
}
/**
* Set a full outer join statement in your query.
*/
fullOuterJoin(table, column1, operation, column2) {
// TODO https://github.com/knex/knex/issues/3949
return this.joinByType('leftJoin', table, column1, operation, column2);
}
/**
* Set a left outer join statement in your query.
*/
leftOuterJoin(table, column1, operation, column2) {
return this.joinByType('leftOuterJoin', table, column1, operation, column2);
}
/**
* Set a right outer join statement in your query.
*/
rightOuterJoin(table, column1, operation, column2) {
return this.joinByType('rightOuterJoin', table, column1, operation, column2);
}
/**
* Set a join raw statement in your query.
*/
joinRaw(sql, bindings) {
if (Is.Undefined(sql)) {
throw new EmptyValueException('joinRaw');
}
this.qb.joinRaw(sql, bindings);
return this;
}
/**
* Set a group by statement in your query.
*/
groupBy(...columns) {
if (Is.Undefined(columns)) {
throw new EmptyColumnException('groupBy');
}
this.qb.groupBy(...columns);
return this;
}
/**
* Set a group by raw statement in your query.
*/
groupByRaw(sql, bindings) {
if (Is.Undefined(sql)) {
throw new EmptyValueException('groupByRaw');
}
this.qb.groupByRaw(sql, bindings);
return this;
}
/**
* Set a having statement in your query.
*/
having(column, operation, value) {
if (Is.Undefined(operation)) {
if (Is.Undefined(column)) {
throw new EmptyColumnException('having');
}
this.qb.having(column);
return this;
}
if (Is.Undefined(value)) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('having');
}
if (Is.Undefined(operation)) {
throw new EmptyValueException('having');
}
this.qb.having(column, '=', operation);
return this;
}
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('having');
}
if (Is.Undefined(value)) {
throw new EmptyValueException('having');
}
this.qb.having(column, operation, value);
return this;
}
/**
* Set a having raw statement in your query.
*/
havingRaw(sql, bindings) {
if (Is.Undefined(sql)) {
throw new EmptyValueException('havingRaw');
}
this.qb.havingRaw(sql, bindings);
return this;
}
/**
* Set a having in statement in your query.
*/
havingIn(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('havingIn');
}
if (Is.Undefined(values)) {
throw new EmptyValueException('havingIn');
}
this.qb.havingIn(column, values);
return this;
}
/**
* Set a having not in statement in your query.
*/
havingNotIn(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('havingNotIn');
}
if (Is.Undefined(values)) {
throw new EmptyValueException('havingNotIn');
}
this.qb.havingNotIn(column, values);
return this;
}
/**
* Set a having between statement in your query.
*/
havingBetween(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('havingBetween');
}
if (Is.Undefined(values?.[0]) || Is.Undefined(values?.[1])) {
throw new EmptyValueException('havingBetween');
}
this.qb.havingBetween(column, values);
return this;
}
/**
* Set a having not between statement in your query.
*/
havingNotBetween(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('havingNotBetween');
}
if (Is.Undefined(values?.[0]) || Is.Undefined(values?.[1])) {
throw new EmptyValueException('havingNotBetween');
}
this.qb.havingNotBetween(column, values);
return this;
}
/**
* Set a having null statement in your query.
*/
havingNull(column) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('havingNull');
}
this.qb.havingNull(column);
return this;
}
/**
* Set a having not null statement in your query.
*/
havingNotNull(column) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('havingNotNull');
}
this.qb.havingNotNull(column);
return this;
}
/**
* Set an or having statement in your query.
*/
orHaving(column, operation, value) {
if (Is.Undefined(operation)) {
if (Is.Undefined(column)) {
throw new EmptyColumnException('orHaving');
}
this.qb.orHaving(column);
return this;
}
if (Is.Undefined(value)) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orHaving');
}
if (Is.Undefined(operation)) {
throw new EmptyValueException('orHaving');
}
this.qb.orHaving(column, '=', operation);
return this;
}
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orHaving');
}
if (Is.Undefined(value)) {
throw new EmptyValueException('orHaving');
}
this.qb.orHaving(column, operation, value);
return this;
}
/**
* Set an or having raw statement in your query.
*/
orHavingRaw(sql, bindings) {
if (Is.Undefined(sql) || !Is.String(sql)) {
throw new EmptyValueException('orHavingRaw');
}
this.qb.orHavingRaw(sql, bindings);
return this;
}
/**
* Set an or having not in statement in your query.
*/
orHavingNotIn(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orHavingNotIn');
}
if (Is.Undefined(values)) {
throw new EmptyValueException('orHavingNotIn');
}
this.qb.orHavingNotIn(column, values);
return this;
}
/**
* Set an or having between statement in your query.
*/
orHavingBetween(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orHavingBetween');
}
if (Is.Undefined(values?.[0]) || Is.Undefined(values?.[1])) {
throw new EmptyValueException('orHavingBetween');
}
this.qb.orHavingBetween(column, values);
return this;
}
/**
* Set an or having not between statement in your query.
*/
orHavingNotBetween(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orHavingNotBetween');
}
if (Is.Undefined(values?.[0]) || Is.Undefined(values?.[1])) {
throw new EmptyValueException('orHavingNotBetween');
}
this.qb.orHavingNotBetween(column, values);
return this;
}
/**
* Set an or having null statement in your query.
*/
orHavingNull(column) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orHavingNull');
}
this.qb.orHavingNull(column);
return this;
}
/**
* Set an or having not null statement in your query.
*/
orHavingNotNull(column) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orHavingNotNull');
}
this.qb.orHavingNotNull(column);
return this;
}
/**
* Set a where statement in your query.
*/
where(statement, operation, value) {
if (Is.Function(statement)) {
const driver = this.clone();
this.qb.where(function () {
statement(driver.setQueryBuilder(this, { useSetQB: true }));
});
return this;
}
if (Is.Undefined(operation)) {
if (Is.Undefined(statement) || !Is.Object(statement)) {
throw new EmptyValueException('where');
}
this.qb.where(statement);
return this;
}
if (Is.Undefined(value)) {
if (Is.Undefined(statement) || !Is.String(statement)) {
throw new EmptyValueException('where');
}
if (this.isUsingJsonSelector(statement)) {
this.whereJson(statement, operation);
return this;
}
this.qb.where(statement, operation);
return this;
}
if (Is.Undefined(statement) || !Is.String(statement)) {
throw new EmptyColumnException('where');
}
if (Is.Undefined(value)) {
throw new EmptyValueException('where');
}
if (this.isUsingJsonSelector(statement)) {
this.whereJson(statement, operation, value);
return this;
}
this.qb.where(statement, operation, value);
return this;
}
/**
* Set a where not statement in your query.
*/
whereNot(statement, value) {
if (Is.Function(statement)) {
const driver = this.clone();
this.qb.whereNot(function () {
statement(driver.setQueryBuilder(this, { useSetQB: true }));
});
return this;
}
if (Is.Undefined(value)) {
if (Is.Undefined(statement) || !Is.Object(statement)) {
throw new EmptyValueException('whereNot');
}
this.qb.whereNot(statement);
return this;
}
if (Is.Undefined(statement) || !Is.String(statement)) {
throw new EmptyColumnException('whereNot');
}
if (Is.Undefined(value)) {
throw new EmptyValueException('whereNot');
}
this.qb.whereNot(statement, value);
return this;
}
/**
* Set a where raw statement in your query.
*/
whereRaw(sql, bindings) {
if (Is.Undefined(sql) || !Is.String(sql)) {
throw new EmptyValueException('whereRaw');
}
this.qb.whereRaw(sql, bindings);
return this;
}
/**
* Set a where exists statement in your query.
*/
whereExists(closure) {
const driver = this.clone();
this.qb.whereExists(function () {
closure(driver.setQueryBuilder(this, { useSetQB: true }));
});
return this;
}
/**
* Set a where not exists statement in your query.
*/
whereNotExists(closure) {
const driver = this.clone();
this.qb.whereNotExists(function () {
closure(driver.setQueryBuilder(this, { useSetQB: true }));
});
return this;
}
/**
* Set a where like statement in your query.
*/
whereLike(column, value) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('whereLike');
}
if (Is.Undefined(value)) {
throw new EmptyValueException('whereLike');
}
this.qb.whereLike(column, value);
return this;
}
/**
* Set a where ILike statement in your query.
*/
whereILike(column, value) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('whereILike');
}
if (Is.Undefined(value)) {
throw new EmptyValueException('whereILike');
}
this.qb.whereILike(column, value);
return this;
}
/**
* Set a where in statement in your query.
*/
whereIn(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('whereIn');
}
if (Is.Undefined(values)) {
throw new EmptyValueException('whereIn');
}
this.qb.whereIn(column, values);
return this;
}
/**
* Set a where not in statement in your query.
*/
whereNotIn(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('whereNotIn');
}
if (Is.Undefined(values)) {
throw new EmptyValueException('whereNotIn');
}
this.qb.whereNotIn(column, values);
return this;
}
/**
* Set a where between statement in your query.
*/
whereBetween(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('whereBetween');
}
if (Is.Undefined(values?.[0]) || Is.Undefined(values?.[1])) {
throw new EmptyValueException('whereBetween');
}
this.qb.whereBetween(column, values);
return this;
}
/**
* Set a where not between statement in your query.
*/
whereNotBetween(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('whereNotBetween');
}
if (Is.Undefined(values?.[0]) || Is.Undefined(values?.[1])) {
throw new EmptyValueException('whereNotBetween');
}
this.qb.whereNotBetween(column, values);
return this;
}
/**
* Set a where null statement in your query.
*/
whereNull(column) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('whereNull');
}
this.qb.whereNull(column);
return this;
}
/**
* Set a where not null statement in your query.
*/
whereNotNull(column) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('whereNotNull');
}
this.qb.whereNotNull(column);
return this;
}
/**
* Set a where json statement in your query.
*/
whereJson(column, operator, value) {
const parsed = this.parseJsonSelector(column);
if (!parsed) {
throw new Error(`Invalid JSON selector: ${column}`);
}
const path = this.parseJsonSelectorToPath(parsed.path);
if (Is.Undefined(value)) {
this.qb.whereJsonPath(parsed.column, path, '=', operator);
return this;
}
this.qb.whereJsonPath(parsed.column, path, operator, value);
return this;
}
/**
* Set a or where statement in your query.
*/
orWhere(statement, operation, value) {
if (Is.Function(statement)) {
const driver = this.clone();
this.qb.orWhere(function () {
statement(driver.setQueryBuilder(this, { useSetQB: true }));
});
return this;
}
if (Is.Undefined(operation)) {
if (Is.Undefined(statement) || !Is.Object(statement)) {
throw new EmptyValueException('orWhere');
}
this.qb.orWhere(statement);
return this;
}
if (Is.Undefined(value)) {
if (Is.Undefined(statement) || !Is.String(statement)) {
throw new EmptyColumnException('orWhere');
}
if (this.isUsingJsonSelector(statement)) {
this.orWhereJson(statement, operation);
return this;
}
this.qb.orWhere(statement, operation);
return this;
}
if (Is.Undefined(statement) || !Is.String(statement)) {
throw new EmptyColumnException('orWhere');
}
if (Is.Undefined(value)) {
throw new EmptyValueException('orWhere');
}
if (this.isUsingJsonSelector(statement)) {
this.orWhereJson(statement, operation, value);
return this;
}
this.qb.orWhere(statement, operation, value);
return this;
}
/**
* Set an or where not statement in your query.
*/
orWhereNot(statement, value) {
if (Is.Function(statement)) {
const driver = this.clone();
this.qb.orWhereNot(function () {
statement(driver.setQueryBuilder(this, { useSetQB: true }));
});
return this;
}
if (Is.Undefined(value)) {
if (Is.Undefined(statement) || !Is.Object(statement)) {
throw new EmptyValueException('orWhereNot');
}
this.qb.orWhereNot(statement);
return this;
}
if (Is.Undefined(statement) || !Is.String(statement)) {
throw new EmptyColumnException('orWhereNot');
}
if (Is.Undefined(value)) {
throw new EmptyValueException('orWhereNot');
}
this.qb.orWhereNot(statement, value);
return this;
}
/**
* Set a or where raw statement in your query.
*/
orWhereRaw(sql, bindings) {
if (Is.Undefined(sql) || !Is.String(sql)) {
throw new EmptyValueException('orWhereRaw');
}
this.qb.orWhereRaw(sql, bindings);
return this;
}
/**
* Set an or where exists statement in your query.
*/
orWhereExists(closure) {
const driver = this.clone();
this.qb.orWhereExists(function () {
closure(driver.setQueryBuilder(this, { useSetQB: true }));
});
return this;
}
/**
* Set an or where not exists statement in your query.
*/
orWhereNotExists(closure) {
const driver = this.clone();
this.qb.orWhereNotExists(function () {
closure(driver.setQueryBuilder(this, { useSetQB: true }));
});
return this;
}
/**
* Set an or where like statement in your query.
*/
orWhereLike(column, value) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orWhereLike');
}
if (Is.Undefined(value)) {
throw new EmptyValueException('orWhereLike');
}
this.qb.orWhereLike(column, value);
return this;
}
/**
* Set an or where ILike statement in your query.
*/
orWhereILike(column, value) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orWhereILike');
}
if (Is.Undefined(value)) {
throw new EmptyValueException('orWhereILike');
}
this.qb.orWhereILike(column, value);
return this;
}
/**
* Set an or where in statement in your query.
*/
orWhereIn(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orWhereIn');
}
if (Is.Undefined(values)) {
throw new EmptyValueException('orWhereIn');
}
this.qb.orWhereIn(column, values);
return this;
}
/**
* Set an or where not in statement in your query.
*/
orWhereNotIn(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orWhereNotIn');
}
if (Is.Undefined(values)) {
throw new EmptyValueException('orWhereNotIn');
}
this.qb.orWhereNotIn(column, values);
return this;
}
/**
* Set an or where between statement in your query.
*/
orWhereBetween(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orWhereBetween');
}
if (Is.Undefined(values?.[0]) || Is.Undefined(values?.[1])) {
throw new EmptyValueException('orWhereBetween');
}
this.qb.orWhereBetween(column, values);
return this;
}
/**
* Set an or where not between statement in your query.
*/
orWhereNotBetween(column, values) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orWhereNotBetween');
}
if (Is.Undefined(values?.[0]) || Is.Undefined(values?.[1])) {
throw new EmptyValueException('orWhereNotBetween');
}
this.qb.orWhereNotBetween(column, values);
return this;
}
/**
* Set an or where null statement in your query.
*/
orWhereNull(column) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orWhereNull');
}
this.qb.orWhereNull(column);
return this;
}
/**
* Set an or where not null statement in your query.
*/
orWhereNotNull(column) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orWhereNotNull');
}
this.qb.orWhereNotNull(column);
return this;
}
/**
* Set an or where json statement in your query.
*/
orWhereJson(column, operator, value) {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orWhereJson');
}
const parsed = this.parseJsonSelector(column);
if (!parsed) {
throw new Error(`Invalid JSON selector: ${column}`);
}
const path = this.parseJsonSelectorToPath(parsed.path);
if (Is.Undefined(value)) {
this.qb.orWhereJsonPath(parsed.column, path, '=', operator);
return this;
}
if (Is.Undefined(value)) {
throw new EmptyValueException('orWhereJson');
}
this.qb.orWhereJsonPath(parsed.column, path, operator, value);
return this;
}
/**
* Convert a json selector path to a valid json path.
*/
parseJsonSelectorToPath(path) {
const parts = path
.split('->')
.map(part => part.trim())
.filter(Boolean);
return parts.reduce((jsonPath, part) => {
if (part === '*') {
return `${jsonPath}[*]`;
}
if (/^\d+$/.test(part)) {
return `${jsonPath}[${part}]`;
}
return `${jsonPath}.${part}`;
}, '$');
}
/**
* Set an order by statement in your query.
*/
orderBy(column, direction = 'ASC') {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('orderBy');
}
this.qb.orderBy(column, direction.toUpperCase());
return this;
}
/**
* Set an order by raw statement in your query.
*/
orderByRaw(sql, bindings) {
if (Is.Undefined(sql) || !Is.String(sql)) {
throw new EmptyValueException('orderByRaw');
}
this.qb.orderByRaw(sql, bindings);
return this;
}
/**
* Order the results easily by the latest date. By default, the result will
* be ordered by the table's "createdAt" column.
*/
latest(column = 'createdAt') {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('latest');
}
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') {
if (Is.Undefined(column) || !Is.String(column)) {
throw new EmptyColumnException('oldest');
}
return this.orderBy(column, 'ASC');
}
/**
* Set the skip number in your query.
*/
offset(number) {
if (Is.Undefined(number) || !Is.Number(number)) {
throw new EmptyValueException('offset');
}
this.qb.offset(number);
return this;
}
/**
* Set the limit number in your query.
*/
limit(number) {
if (Is.Undefined(number) || !Is.Number(number)) {
throw new EmptyValueException('limit');
}
this.qb.limit(number);
return this;
}
}