UNPKG

@adonisjs/lucid

Version:

SQL ORM built on top of Active Record pattern

137 lines (136 loc) 4.34 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 { QueryReporter } from '../query_reporter/index.js'; /** * Query runner exposes the API for executing knex query builder by using the * read/write replicas supported only by Lucid. * * Also it will emit the query data and profile the queries as well. */ export class QueryRunner { client; debug; logData; reporter; constructor(client, debug, logData) { this.client = client; this.debug = debug; this.logData = logData; this.reporter = new QueryReporter(this.client, this.debug, this.logData); } /** * Is query dialect using sqlite database or not */ isUsingSqlite() { return ['sqlite3', 'better-sqlite3', 'libsql'].includes(this.client.dialect.name); } /** * Find if query has a transaction attached to it, by using * `useTransaction` method */ isInTransaction(query) { return query['client'].transacting; } /** * Find if query is a write query or not. */ isWriteQuery(query) { return ['update', 'del', 'delete', 'insert'].includes(query['_method']); } /** * Returns read or write client by inspecting the query */ getQueryClient(query) { return this.isWriteQuery(query) ? this.client.getWriteClient() : this.client.getReadClient(); } /** * Executes the query by handling exceptions and returns it back * gracefully. */ async executeQuery(query) { try { const result = await query; return [undefined, result]; } catch (error) { return [error, undefined]; } } /** * Executes the knex builder directly */ async executeDirectly(query) { /** * We listen for query event on the knex query builder to avoid calling * toSQL too many times and also get the actual time it took to * execute the query */ ; query['once']('query', (sql) => this.reporter.begin({ ...this.logData, ...sql })); const [error, result] = await this.executeQuery(query); this.reporter.end(error); if (error) { throw error; } return result; } /** * Executes query by using a proper read or write connection. */ async executeUsingManagedConnection(query) { const queryClient = this.getQueryClient(query); /** * Acquire connection from the knex connection pool. This is will * use the rounding robin mechanism and force set it on * the query. */ const connection = await queryClient.client.acquireConnection(); query.connection(connection); query['once']('query', (sql) => this.reporter.begin({ ...this.logData, ...sql })); /** * Execute query and report event and profiler data */ const [error, result] = await this.executeQuery(query); this.reporter.end(error); /** * Make sure to always release the connection */ queryClient.client.releaseConnection(connection); /** * If there was an error, raise it or return the result */ if (error) { throw error; } return result; } /** * Run query by managing its life-cycle */ async run(query) { /** * We execute the queries using transaction or using sqlite database * directly. * * - The transaction already has a pre-acquired connection. * - There is no concept of read/write replicas in sqlite. */ if (this.isUsingSqlite() || this.isInTransaction(query)) { return this.executeDirectly(query); } /** * Cannot execute write queries with client in read mode */ if (this.isWriteQuery(query) && this.client.mode === 'read') { throw new Exception('Updates and deletes cannot be performed in read mode'); } return this.executeUsingManagedConnection(query); } }