UNPKG

@adonisjs/lucid

Version:

SQL ORM built on top of Active Record pattern

379 lines (378 loc) 12.9 kB
"use strict"; /* * @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. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Database = void 0; /// <reference path="../../adonis-typings/index.ts" /> const macroable_1 = require("macroable"); const utils_1 = require("@poppinss/utils"); const QueryClient_1 = require("../QueryClient"); const Raw_1 = require("./StaticBuilder/Raw"); const prettyPrint_1 = require("../Helpers/prettyPrint"); const QueryBuilder_1 = require("../Orm/QueryBuilder"); const Manager_1 = require("../Connection/Manager"); const Insert_1 = require("./QueryBuilder/Insert"); const Reference_1 = require("./StaticBuilder/Reference"); const SimplePaginator_1 = require("./Paginator/SimplePaginator"); const Database_1 = require("./QueryBuilder/Database"); /** * Database class exposes the API to manage multiple connections and obtain an instance * of query/transaction clients. */ class Database extends macroable_1.Macroable { constructor(config, logger, profiler, emitter) { super(); Object.defineProperty(this, "config", { enumerable: true, configurable: true, writable: true, value: config }); Object.defineProperty(this, "logger", { enumerable: true, configurable: true, writable: true, value: logger }); Object.defineProperty(this, "profiler", { enumerable: true, configurable: true, writable: true, value: profiler }); Object.defineProperty(this, "emitter", { enumerable: true, configurable: true, writable: true, value: emitter }); /** * Reference to self constructor. TypeScript sucks with "this.constructor" * https://github.com/microsoft/TypeScript/issues/4586 */ Object.defineProperty(this, "Database", { enumerable: true, configurable: true, writable: true, value: Database }); /** * Reference to connections manager */ Object.defineProperty(this, "manager", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** * Primary connection name */ Object.defineProperty(this, "primaryConnectionName", { enumerable: true, configurable: true, writable: true, value: this.config.connection }); /** * Reference to query builders. We expose them, so that they can be * extended from outside using macros. */ Object.defineProperty(this, "DatabaseQueryBuilder", { enumerable: true, configurable: true, writable: true, value: Database_1.DatabaseQueryBuilder }); Object.defineProperty(this, "InsertQueryBuilder", { enumerable: true, configurable: true, writable: true, value: Insert_1.InsertQueryBuilder }); Object.defineProperty(this, "ModelQueryBuilder", { enumerable: true, configurable: true, writable: true, value: QueryBuilder_1.ModelQueryBuilder }); Object.defineProperty(this, "SimplePaginator", { enumerable: true, configurable: true, writable: true, value: SimplePaginator_1.SimplePaginator }); /** * A store of global transactions */ Object.defineProperty(this, "connectionGlobalTransactions", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "hasHealthChecksEnabled", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "prettyPrint", { enumerable: true, configurable: true, writable: true, value: prettyPrint_1.prettyPrint }); this.validateConfig(); this.manager = new Manager_1.ConnectionManager(this.logger, this.emitter); this.registerConnections(); this.findIfHealthChecksAreEnabled(); } /** * Validate config at runtime */ validateConfig() { const validator = new utils_1.ManagerConfigValidator(this.config, 'database', 'config/database'); validator.validateDefault('connection'); validator.validateList('connections', 'connection'); } /** * Compute whether health check is enabled or not after registering the connections. * There are chances that all pre-registered connections are not using health * checks but a dynamic connection is using it. We don't support that use case * for now, since it complicates things a lot and forces us to register the * health checker on demand. */ findIfHealthChecksAreEnabled() { for (let [, conn] of this.manager.connections) { if (conn.config.healthCheck) { this.hasHealthChecksEnabled = true; break; } } } /** * Registering all connections with the manager, so that we can fetch * and connect with them whenver required. */ registerConnections() { Object.keys(this.config.connections).forEach((name) => { this.manager.add(name, this.config.connections[name]); }); } /** * Returns the connection node from the connection manager */ getRawConnection(name) { return this.manager.get(name); } /** * Returns the query client for a given connection */ connection(connection = this.primaryConnectionName, options) { options = options || {}; /** * Use default profiler, when no profiler is defined when obtaining * the query client for a given connection. */ if (!options.profiler) { options.profiler = this.profiler; } /** * Connect is noop when already connected */ this.manager.connect(connection); /** * Disallow modes other than `read` or `write` */ if (options.mode && !['read', 'write'].includes(options.mode)) { throw new utils_1.Exception(`Invalid mode ${options.mode}. Must be read or write`); } /** * Return the global transaction when it already exists. */ if (this.connectionGlobalTransactions.has(connection)) { this.logger.trace({ connection }, 'using pre-existing global transaction connection'); const globalTransactionClient = this.connectionGlobalTransactions.get(connection); return globalTransactionClient; } /** * Fetching connection for the given name */ const rawConnection = this.getRawConnection(connection).connection; /** * Generating query client for a given connection and setting appropriate * mode on it */ this.logger.trace({ connection }, 'creating query client in %s mode', [options.mode || 'dual']); const queryClient = options.mode ? new QueryClient_1.QueryClient(options.mode, rawConnection, this.emitter) : new QueryClient_1.QueryClient('dual', rawConnection, this.emitter); /** * Passing profiler to the query client for profiling queries */ queryClient.profiler = options.profiler; return queryClient; } /** * Returns the knex query builder */ knexQuery() { return this.connection(this.primaryConnectionName).knexQuery(); } /** * Returns the knex raw query builder */ knexRawQuery(sql, bindings) { return this.connection(this.primaryConnectionName).knexRawQuery(sql, bindings); } /** * Returns query builder. Optionally one can define the mode as well */ query(options) { return this.connection(this.primaryConnectionName, options).query(); } /** * Returns insert query builder. Always has to be dual or write mode and * hence it doesn't matter, since in both `dual` and `write` mode, * the `write` connection is always used. */ insertQuery(options) { return this.connection(this.primaryConnectionName, options).insertQuery(); } /** * Returns a query builder instance for a given model. */ modelQuery(model, options) { return this.connection(this.primaryConnectionName, options).modelQuery(model); } /** * Returns an instance of raw query builder. Optionally one can * defined the `read/write` mode in which to execute the * query */ rawQuery(sql, bindings, options) { return this.connection(this.primaryConnectionName, options).rawQuery(sql, bindings); } /** * Returns an instance of raw builder. This raw builder queries * cannot be executed. Use `rawQuery`, if you want to execute * queries raw queries. */ raw(sql, bindings) { return new Raw_1.RawBuilder(sql, bindings); } /** * Returns reference builder. */ ref(reference) { return new Reference_1.ReferenceBuilder(reference, this.connection().getReadClient().client); } /** * Returns instance of a query builder and selects the table */ from(table) { return this.connection().from(table); } /** * Returns insert query builder and selects the table */ table(table) { return this.connection().table(table); } /** * Returns a transaction instance on the default * connection */ transaction(callback, options) { const client = this.connection(); return typeof callback === 'function' ? client.transaction(callback, options) : client.transaction(callback); } /** * Invokes `manager.report` */ report() { return this.manager.report(); } /** * Begin a new global transaction */ async beginGlobalTransaction(connectionName, options) { connectionName = connectionName || this.primaryConnectionName; /** * Return global transaction as it is */ const globalTrx = this.connectionGlobalTransactions.get(connectionName); if (globalTrx) { return globalTrx; } /** * Create a new transaction and store a reference to it */ const trx = await this.connection(connectionName, options).transaction(); this.connectionGlobalTransactions.set(trx.connectionName, trx); /** * Listen for events to drop the reference when transaction * is over */ trx.on('commit', ($trx) => { this.connectionGlobalTransactions.delete($trx.connectionName); }); trx.on('rollback', ($trx) => { this.connectionGlobalTransactions.delete($trx.connectionName); }); return trx; } /** * Commit an existing global transaction */ async commitGlobalTransaction(connectionName) { connectionName = connectionName || this.primaryConnectionName; const trx = this.connectionGlobalTransactions.get(connectionName); if (!trx) { throw new utils_1.Exception([ 'Cannot commit a non-existing global transaction.', ' Make sure you are not calling "commitGlobalTransaction" twice', ].join('')); } await trx.commit(); } /** * Rollback an existing global transaction */ async rollbackGlobalTransaction(connectionName) { connectionName = connectionName || this.primaryConnectionName; const trx = this.connectionGlobalTransactions.get(connectionName); if (!trx) { throw new utils_1.Exception([ 'Cannot rollback a non-existing global transaction.', ' Make sure you are not calling "commitGlobalTransaction" twice', ].join('')); } await trx.rollback(); } } exports.Database = Database; /** * Required by macroable */ Object.defineProperty(Database, "macros", { enumerable: true, configurable: true, writable: true, value: {} }); Object.defineProperty(Database, "getters", { enumerable: true, configurable: true, writable: true, value: {} });