UNPKG

@adonisjs/lucid

Version:

SQL ORM built on top of Active Record pattern

295 lines (294 loc) 8.64 kB
/* * @adonisjs/lucid * * (c) Harminder Virk <virk@adonisjs.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ import { Exception } from '@poppinss/utils'; import { Chainable } from './chainable.js'; import { QueryRunner } from '../../query_runner/index.js'; import { SimplePaginator } from '../paginator/simple_paginator.js'; import * as errors from '../../errors.js'; /** * Wrapping the user function for a query callback and give them * a new instance of the `DatabaseQueryBuilder` and not * knex.QueryBuilder */ const queryCallback = (userFn, keysResolver) => { return (builder) => { /** * Sub queries don't need the client, since client is used to execute the query * and sub-queries are not executed separately. That's why we just pass * an empty object. * * Other option is to have this method for each instance of the class, but this * is waste of resources. */ const query = new DatabaseQueryBuilder(builder, {}, keysResolver); userFn(query); query['applyWhere'](); }; }; /** * Database query builder exposes the API to construct and run queries for selecting, * updating and deleting records. */ export class DatabaseQueryBuilder extends Chainable { client; keysResolver; /** * Custom data someone want to send to the profiler and the * query event */ customReporterData; /** * Control whether to debug the query or not. The initial * value is inherited from the query client */ debugQueries; /** * Required by macroable */ static macros = {}; static getters = {}; constructor(builder, client, keysResolver) { super(builder, queryCallback, keysResolver); this.client = client; this.keysResolver = keysResolver; this.debugQueries = this.client.debug; } /** * Ensures that we are not executing `update` or `del` when using read only * client */ ensureCanPerformWrites() { if (this.client && this.client.mode === 'read') { throw new Exception('Updates and deletes cannot be performed in read mode'); } } /** * Returns the log data */ getQueryData() { return { connection: this.client.connectionName, inTransaction: this.client.isTransaction, ...this.customReporterData, }; } /** * Define custom reporter data. It will be merged with * the existing data */ reporterData(data) { this.customReporterData = data; return this; } /** * Delete rows under the current query */ del(returning) { this.ensureCanPerformWrites(); returning ? this.knexQuery.del(returning) : this.knexQuery.del(); return this; } /** * Alias for [[del]] */ delete(returning) { return this.del(returning); } /** * Clone the current query builder */ clone() { const clonedQuery = new DatabaseQueryBuilder(this.knexQuery.clone(), this.client); this.applyQueryFlags(clonedQuery); clonedQuery.debug(this.debugQueries); this.customReporterData && clonedQuery.reporterData(this.customReporterData); return clonedQuery; } /** * Define returning columns */ returning(columns) { if (this.client.dialect.supportsReturningStatement) { columns = Array.isArray(columns) ? columns.map((column) => this.resolveKey(column)) : this.resolveKey(columns); this.knexQuery.returning(columns); } return this; } /** * Perform update by incrementing value for a given column. Increments * can be clubbed with `update` as well */ increment(column, counter) { this.ensureCanPerformWrites(); this.knexQuery.increment(this.resolveKey(column, true), counter); return this; } /** * Perform update by decrementing value for a given column. Decrements * can be clubbed with `update` as well */ decrement(column, counter) { this.ensureCanPerformWrites(); this.knexQuery.decrement(this.resolveKey(column, true), counter); return this; } /** * Perform update */ update(column, value, returning) { this.ensureCanPerformWrites(); if (value === undefined && returning === undefined) { this.knexQuery.update(this.resolveKey(column, true)); } else if (returning === undefined) { this.knexQuery.update(this.resolveKey(column), value); } else { this.knexQuery.update(this.resolveKey(column), value, returning); } return this; } /** * Fetch and return first results from the results set. This method * will implicitly set a `limit` on the query */ async first() { const result = await this.limit(1)['exec'](); return result[0] || null; } /** * Fetch and return first results from the results set. This method * will implicitly set a `limit` on the query */ async firstOrFail() { const row = await this.first(); if (!row) { throw new errors.E_ROW_NOT_FOUND(); } return row; } /** * Define a query to constraint to be defined when condition is truthy */ ifDialect(dialects, matchCallback, noMatchCallback) { dialects = Array.isArray(dialects) ? dialects : [dialects]; if (dialects.includes(this.client.dialect.name)) { matchCallback(this); } else if (noMatchCallback) { noMatchCallback(this); } return this; } /** * Define a query to constraint to be defined when condition is falsy */ unlessDialect(dialects, matchCallback, noMatchCallback) { dialects = Array.isArray(dialects) ? dialects : [dialects]; if (!dialects.includes(this.client.dialect.name)) { matchCallback(this); } else if (noMatchCallback) { noMatchCallback(this); } return this; } /** * Turn on/off debugging for this query */ debug(debug) { this.debugQueries = debug; return this; } /** * Define query timeout */ timeout(time, options) { this.knexQuery['timeout'](time, options); return this; } /** * Returns SQL query as a string */ toQuery() { this.applyWhere(); return this.knexQuery.toQuery(); } /** * @deprecated * Do not use this method. Instead create a query using the * transaction client directly. * * ```ts * trx.query() * ``` */ useTransaction(transaction) { this.knexQuery.transacting(transaction.knexClient); return this; } /** * Executes the query */ async exec() { this.applyWhere(); return new QueryRunner(this.client, this.debugQueries, this.getQueryData()).run(this.knexQuery); } /** * Paginate through rows inside a given table */ async paginate(page, perPage = 20) { /** * Cast to number */ page = Number(page); perPage = Number(perPage); const countQuery = this.client .query() .from(this.clone().clearOrder().clearLimit().clearOffset().as('subQuery')) .count('* as total'); const aggregates = await countQuery.exec(); const total = aggregates[0].total; const results = total > 0 ? await this.forPage(page, perPage).exec() : []; return new SimplePaginator(total, perPage, page, ...results); } /** * Get sql representation of the query */ toSQL() { this.applyWhere(); return this.knexQuery.toSQL(); } /** * Implementation of `then` for the promise API */ then(resolve, reject) { return this.exec().then(resolve, reject); } /** * Implementation of `catch` for the promise API */ catch(reject) { return this.exec().catch(reject); } /** * Implementation of `finally` for the promise API */ finally(fulfilled) { return this.exec().finally(fulfilled); } /** * Required when Promises are extended */ get [Symbol.toStringTag]() { return this.constructor.name; } }