1 | ;
|
2 | /*
|
3 | * @adonisjs/lucid
|
4 | *
|
5 | * (c) Harminder Virk <virk@adonisjs.com>
|
6 | *
|
7 | * For the full copyright and license information, please view the LICENSE
|
8 | * file that was distributed with this source code.
|
9 | */
|
10 | Object.defineProperty(exports, "__esModule", { value: true });
|
11 | exports.QueryRunner = void 0;
|
12 | const utils_1 = require("@poppinss/utils");
|
13 | const QueryReporter_1 = require("../QueryReporter");
|
14 | /**
|
15 | * Query runner exposes the API for executing knex query builder by using the
|
16 | * read/write replicas supported only by Lucid.
|
17 | *
|
18 | * Also it will emit the query data and profile the queries as well.
|
19 | */
|
20 | class QueryRunner {
|
21 | constructor(client, debug, logData) {
|
22 | Object.defineProperty(this, "client", {
|
23 | enumerable: true,
|
24 | configurable: true,
|
25 | writable: true,
|
26 | value: client
|
27 | });
|
28 | Object.defineProperty(this, "debug", {
|
29 | enumerable: true,
|
30 | configurable: true,
|
31 | writable: true,
|
32 | value: debug
|
33 | });
|
34 | Object.defineProperty(this, "logData", {
|
35 | enumerable: true,
|
36 | configurable: true,
|
37 | writable: true,
|
38 | value: logData
|
39 | });
|
40 | Object.defineProperty(this, "reporter", {
|
41 | enumerable: true,
|
42 | configurable: true,
|
43 | writable: true,
|
44 | value: new QueryReporter_1.QueryReporter(this.client, this.debug, this.logData)
|
45 | });
|
46 | }
|
47 | /**
|
48 | * Is query dialect using sqlite database or not
|
49 | */
|
50 | isUsingSqlite() {
|
51 | return this.client.dialect.name === 'sqlite3';
|
52 | }
|
53 | /**
|
54 | * Find if query has a transaction attached to it, by using
|
55 | * `useTransaction` method
|
56 | */
|
57 | isInTransaction(query) {
|
58 | return query['client'].transacting;
|
59 | }
|
60 | /**
|
61 | * Find if query is a write query or not.
|
62 | */
|
63 | isWriteQuery(query) {
|
64 | return ['update', 'del', 'delete', 'insert'].includes(query['_method']);
|
65 | }
|
66 | /**
|
67 | * Returns read or write client by inspecting the query
|
68 | */
|
69 | getQueryClient(query) {
|
70 | return this.isWriteQuery(query) ? this.client.getWriteClient() : this.client.getReadClient();
|
71 | }
|
72 | /**
|
73 | * Executes the query by handling exceptions and returns it back
|
74 | * gracefully.
|
75 | */
|
76 | async executeQuery(query) {
|
77 | try {
|
78 | const result = await query;
|
79 | return [undefined, result];
|
80 | }
|
81 | catch (error) {
|
82 | return [error, undefined];
|
83 | }
|
84 | }
|
85 | /**
|
86 | * Executes the knex builder directly
|
87 | */
|
88 | async executeDirectly(query) {
|
89 | /**
|
90 | * We listen for query event on the knex query builder to avoid calling
|
91 | * toSQL too many times and also get the actual time it took to
|
92 | * execute the query
|
93 | */
|
94 | query['once']('query', (sql) => this.reporter.begin({ ...this.logData, ...sql }));
|
95 | const [error, result] = await this.executeQuery(query);
|
96 | this.reporter.end(error);
|
97 | if (error) {
|
98 | throw error;
|
99 | }
|
100 | return result;
|
101 | }
|
102 | /**
|
103 | * Executes query by using a proper read or write connection.
|
104 | */
|
105 | async executeUsingManagedConnection(query) {
|
106 | const queryClient = this.getQueryClient(query);
|
107 | /**
|
108 | * Acquire connection from the knex connection pool. This is will
|
109 | * use the rounding robin mechanism and force set it on
|
110 | * the query.
|
111 | */
|
112 | const connection = await queryClient.client.acquireConnection();
|
113 | query.connection(connection);
|
114 | /**
|
115 | * We listen for query event on the knex query builder to avoid calling
|
116 | * toSQL too many times and also get the actual time it took to
|
117 | * execute the query
|
118 | */
|
119 | query['once']('query', (sql) => this.reporter.begin({ ...this.logData, ...sql }));
|
120 | /**
|
121 | * Execute query and report event and profiler data
|
122 | */
|
123 | const [error, result] = await this.executeQuery(query);
|
124 | this.reporter.end(error);
|
125 | /**
|
126 | * Make sure to always release the connection
|
127 | */
|
128 | queryClient.client.releaseConnection(connection);
|
129 | /**
|
130 | * If there was an error, raise it or return the result
|
131 | */
|
132 | if (error) {
|
133 | throw error;
|
134 | }
|
135 | return result;
|
136 | }
|
137 | /**
|
138 | * Run query by managing its life-cycle
|
139 | */
|
140 | async run(query) {
|
141 | /**
|
142 | * We execute the queries using transaction or using sqlite database
|
143 | * directly.
|
144 | *
|
145 | * - The transaction already has a pre-acquired connection.
|
146 | * - There is no concept of read/write replicas in sqlite.
|
147 | */
|
148 | if (this.isUsingSqlite() || this.isInTransaction(query)) {
|
149 | return this.executeDirectly(query);
|
150 | }
|
151 | /**
|
152 | * Cannot execute write queries with client in read mode
|
153 | */
|
154 | if (this.isWriteQuery(query) && this.client.mode === 'read') {
|
155 | throw new utils_1.Exception('Updates and deletes cannot be performed in read mode');
|
156 | }
|
157 | return this.executeUsingManagedConnection(query);
|
158 | }
|
159 | }
|
160 | exports.QueryRunner = QueryRunner;
|