1 | const QueryBuilder = require('./query/builder');
|
2 | const Raw = require('./raw');
|
3 | const { transform } = require('lodash');
|
4 |
|
5 |
|
6 | const orderBys = ['asc', 'desc'];
|
7 |
|
8 |
|
9 | const operators = transform(
|
10 | [
|
11 | '=',
|
12 | '<',
|
13 | '>',
|
14 | '<=',
|
15 | '>=',
|
16 | '<>',
|
17 | '!=',
|
18 | 'like',
|
19 | 'not like',
|
20 | 'between',
|
21 | 'not between',
|
22 | 'ilike',
|
23 | 'not ilike',
|
24 | 'exists',
|
25 | 'not exist',
|
26 | 'rlike',
|
27 | 'not rlike',
|
28 | 'regexp',
|
29 | 'not regexp',
|
30 | '&',
|
31 | '|',
|
32 | '^',
|
33 | '<<',
|
34 | '>>',
|
35 | '~',
|
36 | '~*',
|
37 | '!~',
|
38 | '!~*',
|
39 | '#',
|
40 | '&&',
|
41 | '@>',
|
42 | '<@',
|
43 | '||',
|
44 | '&<',
|
45 | '&>',
|
46 | '-|-',
|
47 | '@@',
|
48 | '!!',
|
49 | ['?', '\\?'],
|
50 | ['?|', '\\?|'],
|
51 | ['?&', '\\?&'],
|
52 | ],
|
53 | (result, key) => {
|
54 | if (Array.isArray(key)) {
|
55 | result[key[0]] = key[1];
|
56 | } else {
|
57 | result[key] = key;
|
58 | }
|
59 | },
|
60 | {}
|
61 | );
|
62 |
|
63 | class Formatter {
|
64 | constructor(client, builder) {
|
65 | this.client = client;
|
66 | this.builder = builder;
|
67 | this.bindings = [];
|
68 | }
|
69 |
|
70 |
|
71 | columnize(target) {
|
72 | const columns = Array.isArray(target) ? target : [target];
|
73 | let str = '',
|
74 | i = -1;
|
75 | while (++i < columns.length) {
|
76 | if (i > 0) str += ', ';
|
77 | str += this.wrap(columns[i]);
|
78 | }
|
79 | return str;
|
80 | }
|
81 |
|
82 |
|
83 |
|
84 | parameterize(values, notSetValue) {
|
85 | if (typeof values === 'function') return this.parameter(values);
|
86 | values = Array.isArray(values) ? values : [values];
|
87 | let str = '',
|
88 | i = -1;
|
89 | while (++i < values.length) {
|
90 | if (i > 0) str += ', ';
|
91 | str += this.parameter(values[i] === undefined ? notSetValue : values[i]);
|
92 | }
|
93 | return str;
|
94 | }
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | values(values) {
|
105 | if (Array.isArray(values)) {
|
106 | if (Array.isArray(values[0])) {
|
107 | return `(${values
|
108 | .map((value) => `(${this.parameterize(value)})`)
|
109 | .join(', ')})`;
|
110 | }
|
111 | return `(${this.parameterize(values)})`;
|
112 | }
|
113 |
|
114 | if (values instanceof Raw) {
|
115 | return `(${this.parameter(values)})`;
|
116 | }
|
117 |
|
118 | return this.parameter(values);
|
119 | }
|
120 |
|
121 |
|
122 |
|
123 | parameter(value) {
|
124 | if (typeof value === 'function') {
|
125 | return this.outputQuery(this.compileCallback(value), true);
|
126 | }
|
127 | return this.unwrapRaw(value, true) || '?';
|
128 | }
|
129 |
|
130 | unwrapRaw(value, isParameter) {
|
131 | let query;
|
132 | if (value instanceof QueryBuilder) {
|
133 | query = this.client.queryCompiler(value).toSQL();
|
134 | if (query.bindings) {
|
135 | this.bindings = this.bindings.concat(query.bindings);
|
136 | }
|
137 | return this.outputQuery(query, isParameter);
|
138 | }
|
139 | if (value instanceof Raw) {
|
140 | value.client = this.client;
|
141 | if (this.builder._queryContext) {
|
142 | value.queryContext = () => {
|
143 | return this.builder._queryContext;
|
144 | };
|
145 | }
|
146 |
|
147 | query = value.toSQL();
|
148 | if (query.bindings) {
|
149 | this.bindings = this.bindings.concat(query.bindings);
|
150 | }
|
151 | return query.sql;
|
152 | }
|
153 | if (isParameter) {
|
154 | this.bindings.push(value);
|
155 | }
|
156 | }
|
157 |
|
158 | |
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 | rawOrFn(value, method) {
|
166 | if (typeof value === 'function') {
|
167 | return this.outputQuery(this.compileCallback(value, method));
|
168 | }
|
169 | return this.unwrapRaw(value) || '';
|
170 | }
|
171 |
|
172 |
|
173 |
|
174 | wrap(value) {
|
175 | const raw = this.unwrapRaw(value);
|
176 | if (raw) return raw;
|
177 | switch (typeof value) {
|
178 | case 'function':
|
179 | return this.outputQuery(this.compileCallback(value), true);
|
180 | case 'object':
|
181 | return this.parseObject(value);
|
182 | case 'number':
|
183 | return value;
|
184 | default:
|
185 | return this.wrapString(value + '');
|
186 | }
|
187 | }
|
188 |
|
189 | wrapAsIdentifier(value) {
|
190 | const queryContext = this.builder.queryContext();
|
191 | return this.client.wrapIdentifier((value || '').trim(), queryContext);
|
192 | }
|
193 |
|
194 | alias(first, second) {
|
195 | return first + ' as ' + second;
|
196 | }
|
197 |
|
198 | operator(value) {
|
199 | const raw = this.unwrapRaw(value);
|
200 | if (raw) return raw;
|
201 | const operator = operators[(value || '').toLowerCase()];
|
202 | if (!operator) {
|
203 | throw new TypeError(`The operator "${value}" is not permitted`);
|
204 | }
|
205 | return operator;
|
206 | }
|
207 |
|
208 |
|
209 | direction(value) {
|
210 | const raw = this.unwrapRaw(value);
|
211 | if (raw) return raw;
|
212 | return orderBys.indexOf((value || '').toLowerCase()) !== -1 ? value : 'asc';
|
213 | }
|
214 |
|
215 |
|
216 | compileCallback(callback, method) {
|
217 | const { client } = this;
|
218 |
|
219 |
|
220 | const builder = client.queryBuilder();
|
221 | callback.call(builder, builder);
|
222 |
|
223 |
|
224 | const compiler = client.queryCompiler(builder);
|
225 | compiler.formatter = this;
|
226 |
|
227 |
|
228 | return compiler.toSQL(method || builder._method || 'select');
|
229 | }
|
230 |
|
231 |
|
232 | outputQuery(compiled, isParameter) {
|
233 | let sql = compiled.sql || '';
|
234 | if (sql) {
|
235 | if (
|
236 | (compiled.method === 'select' || compiled.method === 'first') &&
|
237 | (isParameter || compiled.as)
|
238 | ) {
|
239 | sql = `(${sql})`;
|
240 | if (compiled.as) return this.alias(sql, this.wrap(compiled.as));
|
241 | }
|
242 | }
|
243 | return sql;
|
244 | }
|
245 |
|
246 |
|
247 | parseObject(obj) {
|
248 | const ret = [];
|
249 | for (const alias in obj) {
|
250 | const queryOrIdentifier = obj[alias];
|
251 |
|
252 | if (typeof queryOrIdentifier === 'function') {
|
253 | const compiled = this.compileCallback(queryOrIdentifier);
|
254 | compiled.as = alias;
|
255 | ret.push(this.outputQuery(compiled, true));
|
256 | } else if (queryOrIdentifier instanceof QueryBuilder) {
|
257 | ret.push(
|
258 | this.alias(
|
259 | `(${this.wrap(queryOrIdentifier)})`,
|
260 | this.wrapAsIdentifier(alias)
|
261 | )
|
262 | );
|
263 | } else {
|
264 | ret.push(
|
265 | this.alias(this.wrap(queryOrIdentifier), this.wrapAsIdentifier(alias))
|
266 | );
|
267 | }
|
268 | }
|
269 | return ret.join(', ');
|
270 | }
|
271 |
|
272 |
|
273 | wrapString(value) {
|
274 | const asIndex = value.toLowerCase().indexOf(' as ');
|
275 | if (asIndex !== -1) {
|
276 | const first = value.slice(0, asIndex);
|
277 | const second = value.slice(asIndex + 4);
|
278 | return this.alias(this.wrap(first), this.wrapAsIdentifier(second));
|
279 | }
|
280 | const wrapped = [];
|
281 | let i = -1;
|
282 | const segments = value.split('.');
|
283 | while (++i < segments.length) {
|
284 | value = segments[i];
|
285 | if (i === 0 && segments.length > 1) {
|
286 | wrapped.push(this.wrap((value || '').trim()));
|
287 | } else {
|
288 | wrapped.push(this.wrapAsIdentifier(value));
|
289 | }
|
290 | }
|
291 | return wrapped.join('.');
|
292 | }
|
293 | }
|
294 |
|
295 | module.exports = Formatter;
|