UNPKG

4.36 kBJavaScriptView Raw
1// Raw
2// -------
3const inherits = require('inherits');
4const helpers = require('./helpers');
5const { EventEmitter } = require('events');
6const debug = require('debug');
7
8const {
9 assign,
10 reduce,
11 isPlainObject,
12 isObject,
13 isUndefined,
14 isNumber,
15} = require('lodash');
16const saveAsyncStack = require('./util/save-async-stack');
17const uuid = require('uuid');
18
19const debugBindings = debug('knex:bindings');
20
21function Raw(client) {
22 this.client = client;
23
24 this.sql = '';
25 this.bindings = [];
26
27 // Todo: Deprecate
28 this._wrappedBefore = undefined;
29 this._wrappedAfter = undefined;
30 if (client && client.config) {
31 this._debug = client.config.debug;
32 saveAsyncStack(this, 4);
33 }
34}
35
36inherits(Raw, EventEmitter);
37
38assign(Raw.prototype, {
39 set(sql, bindings) {
40 this.sql = sql;
41 this.bindings =
42 (isObject(bindings) && !bindings.toSQL) || isUndefined(bindings)
43 ? bindings
44 : [bindings];
45
46 return this;
47 },
48
49 timeout(ms, { cancel } = {}) {
50 if (isNumber(ms) && ms > 0) {
51 this._timeout = ms;
52 if (cancel) {
53 this.client.assertCanCancelQuery();
54 this._cancelOnTimeout = true;
55 }
56 }
57 return this;
58 },
59
60 // Wraps the current sql with `before` and `after`.
61 wrap(before, after) {
62 this._wrappedBefore = before;
63 this._wrappedAfter = after;
64 return this;
65 },
66
67 // Calls `toString` on the Knex object.
68 toString() {
69 return this.toQuery();
70 },
71
72 // Returns the raw sql for the query.
73 toSQL(method, tz) {
74 let obj;
75 const formatter = this.client.formatter(this);
76
77 if (Array.isArray(this.bindings)) {
78 obj = replaceRawArrBindings(this, formatter);
79 } else if (this.bindings && isPlainObject(this.bindings)) {
80 obj = replaceKeyBindings(this, formatter);
81 } else {
82 obj = {
83 method: 'raw',
84 sql: this.sql,
85 bindings: isUndefined(this.bindings) ? [] : [this.bindings],
86 };
87 }
88
89 if (this._wrappedBefore) {
90 obj.sql = this._wrappedBefore + obj.sql;
91 }
92 if (this._wrappedAfter) {
93 obj.sql = obj.sql + this._wrappedAfter;
94 }
95
96 obj.options = reduce(this._options, assign, {});
97
98 if (this._timeout) {
99 obj.timeout = this._timeout;
100 if (this._cancelOnTimeout) {
101 obj.cancelOnTimeout = this._cancelOnTimeout;
102 }
103 }
104
105 obj.bindings = obj.bindings || [];
106 if (helpers.containsUndefined(obj.bindings)) {
107 const undefinedBindingIndices = helpers.getUndefinedIndices(
108 this.bindings
109 );
110 debugBindings(obj.bindings);
111 throw new Error(
112 `Undefined binding(s) detected for keys [${undefinedBindingIndices}] when compiling RAW query: ${obj.sql}`
113 );
114 }
115
116 obj.__knexQueryUid = uuid.v1();
117
118 return obj;
119 },
120});
121
122function replaceRawArrBindings(raw, formatter) {
123 const expectedBindings = raw.bindings.length;
124 const values = raw.bindings;
125 let index = 0;
126
127 const sql = raw.sql.replace(/\\?\?\??/g, function(match) {
128 if (match === '\\?') {
129 return match;
130 }
131
132 const value = values[index++];
133
134 if (match === '??') {
135 return formatter.columnize(value);
136 }
137 return formatter.parameter(value);
138 });
139
140 if (expectedBindings !== index) {
141 throw new Error(`Expected ${expectedBindings} bindings, saw ${index}`);
142 }
143
144 return {
145 method: 'raw',
146 sql,
147 bindings: formatter.bindings,
148 };
149}
150
151function replaceKeyBindings(raw, formatter) {
152 const values = raw.bindings;
153 const regex = /\\?(:(\w+):(?=::)|:(\w+):(?!:)|:(\w+))/g;
154
155 const sql = raw.sql.replace(regex, function(match, p1, p2, p3, p4) {
156 if (match !== p1) {
157 return p1;
158 }
159
160 const part = p2 || p3 || p4;
161 const key = match.trim();
162 const isIdentifier = key[key.length - 1] === ':';
163 const value = values[part];
164
165 if (value === undefined) {
166 if (Object.prototype.hasOwnProperty.call(values, part)) {
167 formatter.bindings.push(value);
168 }
169
170 return match;
171 }
172
173 if (isIdentifier) {
174 return match.replace(p1, formatter.columnize(value));
175 }
176
177 return match.replace(p1, formatter.parameter(value));
178 });
179
180 return {
181 method: 'raw',
182 sql,
183 bindings: formatter.bindings,
184 };
185}
186
187// Allow the `Raw` object to be utilized with full access to the relevant
188// promise API.
189require('./interface')(Raw);
190helpers.addQueryContext(Raw);
191
192module.exports = Raw;