UNPKG

23.3 kBJavaScriptView Raw
1// Query Compiler
2// -------
3const helpers = require('../helpers');
4const Raw = require('../raw');
5const QueryBuilder = require('./builder');
6const JoinClause = require('./joinclause');
7const debug = require('debug');
8
9const {
10 assign,
11 bind,
12 compact,
13 groupBy,
14 isEmpty,
15 isString,
16 isUndefined,
17 map,
18 omitBy,
19 reduce,
20 has,
21} = require('lodash');
22const uuid = require('uuid');
23
24const debugBindings = debug('knex:bindings');
25
26// The "QueryCompiler" takes all of the query statements which
27// have been gathered in the "QueryBuilder" and turns them into a
28// properly formatted / bound query string.
29function QueryCompiler(client, builder) {
30 this.client = client;
31 this.method = builder._method || 'select';
32 this.options = builder._options;
33 this.single = builder._single;
34 this.timeout = builder._timeout || false;
35 this.cancelOnTimeout = builder._cancelOnTimeout || false;
36 this.grouped = groupBy(builder._statements, 'grouping');
37 this.formatter = client.formatter(builder);
38}
39
40const components = [
41 'columns',
42 'join',
43 'where',
44 'union',
45 'group',
46 'having',
47 'order',
48 'limit',
49 'offset',
50 'lock',
51 'waitMode',
52];
53
54assign(QueryCompiler.prototype, {
55 // Used when the insert call is empty.
56 _emptyInsertValue: 'default values',
57
58 // Collapse the builder into a single object
59 toSQL(method, tz) {
60 this._undefinedInWhereClause = false;
61 this.undefinedBindingsInfo = [];
62
63 method = method || this.method;
64 const val = this[method]() || '';
65
66 const query = {
67 method,
68 options: reduce(this.options, assign, {}),
69 timeout: this.timeout,
70 cancelOnTimeout: this.cancelOnTimeout,
71 bindings: this.formatter.bindings || [],
72 __knexQueryUid: uuid.v1(),
73 };
74
75 Object.defineProperties(query, {
76 toNative: {
77 value: () => {
78 return {
79 sql: this.client.positionBindings(query.sql),
80 bindings: this.client.prepBindings(query.bindings),
81 };
82 },
83 enumerable: false,
84 },
85 });
86
87 if (isString(val)) {
88 query.sql = val;
89 } else {
90 assign(query, val);
91 }
92
93 if (method === 'select' || method === 'first') {
94 if (this.single.as) {
95 query.as = this.single.as;
96 }
97 }
98
99 if (this._undefinedInWhereClause) {
100 debugBindings(query.bindings);
101 throw new Error(
102 `Undefined binding(s) detected when compiling ` +
103 `${method.toUpperCase()}. Undefined column(s): [${this.undefinedBindingsInfo.join(
104 ', '
105 )}] query: ${query.sql}`
106 );
107 }
108
109 return query;
110 },
111
112 // Compiles the `select` statement, or nested sub-selects by calling each of
113 // the component compilers, trimming out the empties, and returning a
114 // generated query string.
115 select() {
116 let sql = this.with();
117
118 const statements = components.map((component) => this[component](this));
119 sql += compact(statements).join(' ');
120 return sql;
121 },
122
123 pluck() {
124 let toPluck = this.single.pluck;
125 if (toPluck.indexOf('.') !== -1) {
126 toPluck = toPluck.split('.').slice(-1)[0];
127 }
128 return {
129 sql: this.select(),
130 pluck: toPluck,
131 };
132 },
133
134 // Compiles an "insert" query, allowing for multiple
135 // inserts using a single query statement.
136 insert() {
137 const insertValues = this.single.insert || [];
138 let sql = this.with() + `insert into ${this.tableName} `;
139 if (Array.isArray(insertValues)) {
140 if (insertValues.length === 0) {
141 return '';
142 }
143 } else if (typeof insertValues === 'object' && isEmpty(insertValues)) {
144 return sql + this._emptyInsertValue;
145 }
146
147 const insertData = this._prepInsert(insertValues);
148 if (typeof insertData === 'string') {
149 sql += insertData;
150 } else {
151 if (insertData.columns.length) {
152 sql += `(${this.formatter.columnize(insertData.columns)}`;
153 sql += ') values (';
154 let i = -1;
155 while (++i < insertData.values.length) {
156 if (i !== 0) sql += '), (';
157 sql += this.formatter.parameterize(
158 insertData.values[i],
159 this.client.valueForUndefined
160 );
161 }
162 sql += ')';
163 } else if (insertValues.length === 1 && insertValues[0]) {
164 sql += this._emptyInsertValue;
165 } else {
166 sql = '';
167 }
168 }
169 return sql;
170 },
171
172 // Compiles the "update" query.
173 update() {
174 // Make sure tableName is processed by the formatter first.
175 const withSQL = this.with();
176 const { tableName } = this;
177 const updateData = this._prepUpdate(this.single.update);
178 const wheres = this.where();
179 return (
180 withSQL +
181 `update ${this.single.only ? 'only ' : ''}${tableName}` +
182 ' set ' +
183 updateData.join(', ') +
184 (wheres ? ` ${wheres}` : '')
185 );
186 },
187
188 // Compiles the columns in the query, specifying if an item was distinct.
189 columns() {
190 let distinctClause = '';
191 if (this.onlyUnions()) return '';
192 const columns = this.grouped.columns || [];
193 let i = -1,
194 sql = [];
195 if (columns) {
196 while (++i < columns.length) {
197 const stmt = columns[i];
198 if (stmt.distinct) distinctClause = 'distinct ';
199 if (stmt.distinctOn) {
200 distinctClause = this.distinctOn(stmt.value);
201 continue;
202 }
203 if (stmt.type === 'aggregate') {
204 sql.push(...this.aggregate(stmt));
205 } else if (stmt.type === 'aggregateRaw') {
206 sql.push(this.aggregateRaw(stmt));
207 } else if (stmt.value && stmt.value.length > 0) {
208 sql.push(this.formatter.columnize(stmt.value));
209 }
210 }
211 }
212 if (sql.length === 0) sql = ['*'];
213 return (
214 `select ${distinctClause}` +
215 sql.join(', ') +
216 (this.tableName
217 ? ` from ${this.single.only ? 'only ' : ''}${this.tableName}`
218 : '')
219 );
220 },
221
222 _aggregate(stmt, { aliasSeparator = ' as ', distinctParentheses } = {}) {
223 const value = stmt.value;
224 const method = stmt.method;
225 const distinct = stmt.aggregateDistinct ? 'distinct ' : '';
226 const wrap = (identifier) => this.formatter.wrap(identifier);
227 const addAlias = (value, alias) => {
228 if (alias) {
229 return value + aliasSeparator + wrap(alias);
230 }
231 return value;
232 };
233 const aggregateArray = (value, alias) => {
234 let columns = value.map(wrap).join(', ');
235 if (distinct) {
236 const openParen = distinctParentheses ? '(' : ' ';
237 const closeParen = distinctParentheses ? ')' : '';
238 columns = distinct.trim() + openParen + columns + closeParen;
239 }
240 const aggregated = `${method}(${columns})`;
241 return addAlias(aggregated, alias);
242 };
243 const aggregateString = (value, alias) => {
244 const aggregated = `${method}(${distinct + wrap(value)})`;
245 return addAlias(aggregated, alias);
246 };
247
248 if (Array.isArray(value)) {
249 return [aggregateArray(value)];
250 }
251
252 if (typeof value === 'object') {
253 if (stmt.alias) {
254 throw new Error('When using an object explicit alias can not be used');
255 }
256 return Object.entries(value).map(([alias, column]) => {
257 if (Array.isArray(column)) {
258 return aggregateArray(column, alias);
259 }
260 return aggregateString(column, alias);
261 });
262 }
263
264 // Allows us to speciy an alias for the aggregate types.
265 const splitOn = value.toLowerCase().indexOf(' as ');
266 let column = value;
267 let { alias } = stmt;
268 if (splitOn !== -1) {
269 column = value.slice(0, splitOn);
270 if (alias) {
271 throw new Error(`Found multiple aliases for same column: ${column}`);
272 }
273 alias = value.slice(splitOn + 4);
274 }
275 return [aggregateString(column, alias)];
276 },
277
278 aggregate(stmt) {
279 return this._aggregate(stmt);
280 },
281
282 aggregateRaw(stmt) {
283 const distinct = stmt.aggregateDistinct ? 'distinct ' : '';
284 return `${stmt.method}(${distinct + this.formatter.unwrapRaw(stmt.value)})`;
285 },
286
287 // Compiles all each of the `join` clauses on the query,
288 // including any nested join queries.
289 join() {
290 let sql = '';
291 let i = -1;
292 const joins = this.grouped.join;
293 if (!joins) return '';
294 while (++i < joins.length) {
295 const join = joins[i];
296 const table = join.schema ? `${join.schema}.${join.table}` : join.table;
297 if (i > 0) sql += ' ';
298 if (join.joinType === 'raw') {
299 sql += this.formatter.unwrapRaw(join.table);
300 } else {
301 sql += join.joinType + ' join ' + this.formatter.wrap(table);
302 let ii = -1;
303 while (++ii < join.clauses.length) {
304 const clause = join.clauses[ii];
305 if (ii > 0) {
306 sql += ` ${clause.bool} `;
307 } else {
308 sql += ` ${clause.type === 'onUsing' ? 'using' : 'on'} `;
309 }
310 const val = this[clause.type].call(this, clause);
311 if (val) {
312 sql += val;
313 }
314 }
315 }
316 }
317 return sql;
318 },
319
320 onBetween(statement) {
321 return (
322 this.formatter.wrap(statement.column) +
323 ' ' +
324 this._not(statement, 'between') +
325 ' ' +
326 map(statement.value, bind(this.formatter.parameter, this.formatter)).join(
327 ' and '
328 )
329 );
330 },
331
332 onNull(statement) {
333 return (
334 this.formatter.wrap(statement.column) +
335 ' is ' +
336 this._not(statement, 'null')
337 );
338 },
339
340 onExists(statement) {
341 return (
342 this._not(statement, 'exists') +
343 ' (' +
344 this.formatter.rawOrFn(statement.value) +
345 ')'
346 );
347 },
348
349 onIn(statement) {
350 if (Array.isArray(statement.column)) return this.multiOnIn(statement);
351 return (
352 this.formatter.wrap(statement.column) +
353 ' ' +
354 this._not(statement, 'in ') +
355 this.wrap(this.formatter.parameterize(statement.value))
356 );
357 },
358
359 multiOnIn(statement) {
360 let i = -1,
361 sql = `(${this.formatter.columnize(statement.column)}) `;
362 sql += this._not(statement, 'in ') + '((';
363 while (++i < statement.value.length) {
364 if (i !== 0) sql += '),(';
365 sql += this.formatter.parameterize(statement.value[i]);
366 }
367 return sql + '))';
368 },
369
370 // Compiles all `where` statements on the query.
371 where() {
372 const wheres = this.grouped.where;
373 if (!wheres) return;
374 const sql = [];
375 let i = -1;
376 while (++i < wheres.length) {
377 const stmt = wheres[i];
378 if (
379 Object.prototype.hasOwnProperty.call(stmt, 'value') &&
380 helpers.containsUndefined(stmt.value)
381 ) {
382 this.undefinedBindingsInfo.push(stmt.column);
383 this._undefinedInWhereClause = true;
384 }
385 const val = this[stmt.type](stmt);
386 if (val) {
387 if (sql.length === 0) {
388 sql[0] = 'where';
389 } else {
390 sql.push(stmt.bool);
391 }
392 sql.push(val);
393 }
394 }
395 return sql.length > 1 ? sql.join(' ') : '';
396 },
397
398 group() {
399 return this._groupsOrders('group');
400 },
401
402 order() {
403 return this._groupsOrders('order');
404 },
405
406 // Compiles the `having` statements.
407 having() {
408 const havings = this.grouped.having;
409 if (!havings) return '';
410 const sql = ['having'];
411 for (let i = 0, l = havings.length; i < l; i++) {
412 const s = havings[i];
413 const val = this[s.type](s);
414 if (val) {
415 if (sql.length === 0) {
416 sql[0] = 'where';
417 }
418 if (sql.length > 1 || (sql.length === 1 && sql[0] !== 'having')) {
419 sql.push(s.bool);
420 }
421 sql.push(val);
422 }
423 }
424 return sql.length > 1 ? sql.join(' ') : '';
425 },
426
427 havingRaw(statement) {
428 return this._not(statement, '') + this.formatter.unwrapRaw(statement.value);
429 },
430
431 havingWrapped(statement) {
432 const val = this.formatter.rawOrFn(statement.value, 'where');
433 return (val && this._not(statement, '') + '(' + val.slice(6) + ')') || '';
434 },
435
436 havingBasic(statement) {
437 return (
438 this._not(statement, '') +
439 this.formatter.wrap(statement.column) +
440 ' ' +
441 this.formatter.operator(statement.operator) +
442 ' ' +
443 this.formatter.parameter(statement.value)
444 );
445 },
446
447 havingNull(statement) {
448 return (
449 this.formatter.wrap(statement.column) +
450 ' is ' +
451 this._not(statement, 'null')
452 );
453 },
454
455 havingExists(statement) {
456 return (
457 this._not(statement, 'exists') +
458 ' (' +
459 this.formatter.rawOrFn(statement.value) +
460 ')'
461 );
462 },
463
464 havingBetween(statement) {
465 return (
466 this.formatter.wrap(statement.column) +
467 ' ' +
468 this._not(statement, 'between') +
469 ' ' +
470 map(statement.value, bind(this.formatter.parameter, this.formatter)).join(
471 ' and '
472 )
473 );
474 },
475
476 havingIn(statement) {
477 if (Array.isArray(statement.column)) return this.multiHavingIn(statement);
478 return (
479 this.formatter.wrap(statement.column) +
480 ' ' +
481 this._not(statement, 'in ') +
482 this.wrap(this.formatter.parameterize(statement.value))
483 );
484 },
485
486 multiHavingIn(statement) {
487 let i = -1,
488 sql = `(${this.formatter.columnize(statement.column)}) `;
489 sql += this._not(statement, 'in ') + '((';
490 while (++i < statement.value.length) {
491 if (i !== 0) sql += '),(';
492 sql += this.formatter.parameterize(statement.value[i]);
493 }
494 return sql + '))';
495 },
496
497 // Compile the "union" queries attached to the main query.
498 union() {
499 const onlyUnions = this.onlyUnions();
500 const unions = this.grouped.union;
501 if (!unions) return '';
502 let sql = '';
503 for (let i = 0, l = unions.length; i < l; i++) {
504 const union = unions[i];
505 if (i > 0) sql += ' ';
506 if (i > 0 || !onlyUnions) sql += union.clause + ' ';
507 const statement = this.formatter.rawOrFn(union.value);
508 if (statement) {
509 if (union.wrap) sql += '(';
510 sql += statement;
511 if (union.wrap) sql += ')';
512 }
513 }
514 return sql;
515 },
516
517 // If we haven't specified any columns or a `tableName`, we're assuming this
518 // is only being used for unions.
519 onlyUnions() {
520 return !this.grouped.columns && this.grouped.union && !this.tableName;
521 },
522
523 limit() {
524 const noLimit = !this.single.limit && this.single.limit !== 0;
525 if (noLimit) return '';
526 return `limit ${this.formatter.parameter(this.single.limit)}`;
527 },
528
529 offset() {
530 if (!this.single.offset) return '';
531 return `offset ${this.formatter.parameter(this.single.offset)}`;
532 },
533
534 // Compiles a `delete` query.
535 del() {
536 // Make sure tableName is processed by the formatter first.
537 const { tableName } = this;
538 const withSQL = this.with();
539 const wheres = this.where();
540 return (
541 withSQL +
542 `delete from ${this.single.only ? 'only ' : ''}${tableName}` +
543 (wheres ? ` ${wheres}` : '')
544 );
545 },
546
547 // Compiles a `truncate` query.
548 truncate() {
549 return `truncate ${this.tableName}`;
550 },
551
552 // Compiles the "locks".
553 lock() {
554 if (this.single.lock) {
555 return this[this.single.lock]();
556 }
557 },
558
559 // Compiles the wait mode on the locks.
560 waitMode() {
561 if (this.single.waitMode) {
562 return this[this.single.waitMode]();
563 }
564 },
565
566 // Fail on unsupported databases
567 skipLocked() {
568 throw new Error(
569 '.skipLocked() is currently only supported on MySQL 8.0+ and PostgreSQL 9.5+'
570 );
571 },
572
573 // Fail on unsupported databases
574 noWait() {
575 throw new Error(
576 '.noWait() is currently only supported on MySQL 8.0+, MariaDB 10.3.0+ and PostgreSQL 9.5+'
577 );
578 },
579
580 distinctOn(value) {
581 throw new Error('.distinctOn() is currently only supported on PostgreSQL');
582 },
583
584 // On Clause
585 // ------
586
587 onWrapped(clause) {
588 const self = this;
589
590 const wrapJoin = new JoinClause();
591 clause.value.call(wrapJoin, wrapJoin);
592
593 let sql = '';
594 wrapJoin.clauses.forEach(function(wrapClause, ii) {
595 if (ii > 0) {
596 sql += ` ${wrapClause.bool} `;
597 }
598 const val = self[wrapClause.type](wrapClause);
599 if (val) {
600 sql += val;
601 }
602 });
603
604 if (sql.length) {
605 return `(${sql})`;
606 }
607 return '';
608 },
609
610 onBasic(clause) {
611 return (
612 this.formatter.wrap(clause.column) +
613 ' ' +
614 this.formatter.operator(clause.operator) +
615 ' ' +
616 this.formatter.wrap(clause.value)
617 );
618 },
619
620 onVal(clause) {
621 return (
622 this.formatter.wrap(clause.column) +
623 ' ' +
624 this.formatter.operator(clause.operator) +
625 ' ' +
626 this.formatter.parameter(clause.value)
627 );
628 },
629
630 onRaw(clause) {
631 return this.formatter.unwrapRaw(clause.value);
632 },
633
634 onUsing(clause) {
635 return '(' + this.formatter.columnize(clause.column) + ')';
636 },
637
638 // Where Clause
639 // ------
640
641 whereIn(statement) {
642 let columns = null;
643 if (Array.isArray(statement.column)) {
644 columns = `(${this.formatter.columnize(statement.column)})`;
645 } else {
646 columns = this.formatter.wrap(statement.column);
647 }
648
649 const values = this.formatter.values(statement.value);
650 return `${columns} ${this._not(statement, 'in ')}${values}`;
651 },
652
653 whereNull(statement) {
654 return (
655 this.formatter.wrap(statement.column) +
656 ' is ' +
657 this._not(statement, 'null')
658 );
659 },
660
661 // Compiles a basic "where" clause.
662 whereBasic(statement) {
663 return (
664 this._not(statement, '') +
665 this.formatter.wrap(statement.column) +
666 ' ' +
667 this.formatter.operator(statement.operator) +
668 ' ' +
669 (statement.asColumn
670 ? this.formatter.wrap(statement.value)
671 : this.formatter.parameter(statement.value))
672 );
673 },
674
675 whereExists(statement) {
676 return (
677 this._not(statement, 'exists') +
678 ' (' +
679 this.formatter.rawOrFn(statement.value) +
680 ')'
681 );
682 },
683
684 whereWrapped(statement) {
685 const val = this.formatter.rawOrFn(statement.value, 'where');
686 return (val && this._not(statement, '') + '(' + val.slice(6) + ')') || '';
687 },
688
689 whereBetween(statement) {
690 return (
691 this.formatter.wrap(statement.column) +
692 ' ' +
693 this._not(statement, 'between') +
694 ' ' +
695 map(statement.value, bind(this.formatter.parameter, this.formatter)).join(
696 ' and '
697 )
698 );
699 },
700
701 // Compiles a "whereRaw" query.
702 whereRaw(statement) {
703 return this._not(statement, '') + this.formatter.unwrapRaw(statement.value);
704 },
705
706 wrap(str) {
707 if (str.charAt(0) !== '(') return `(${str})`;
708 return str;
709 },
710
711 // Compiles all `with` statements on the query.
712 with() {
713 if (!this.grouped.with || !this.grouped.with.length) {
714 return '';
715 }
716 const withs = this.grouped.with;
717 if (!withs) return;
718 const sql = [];
719 let i = -1;
720 let isRecursive = false;
721 while (++i < withs.length) {
722 const stmt = withs[i];
723 if (stmt.recursive) {
724 isRecursive = true;
725 }
726 const val = this[stmt.type](stmt);
727 sql.push(val);
728 }
729 return `with ${isRecursive ? 'recursive ' : ''}${sql.join(', ')} `;
730 },
731
732 withWrapped(statement) {
733 const val = this.formatter.rawOrFn(statement.value);
734 return (
735 (val &&
736 this.formatter.columnize(statement.alias) + ' as (' + val + ')') ||
737 ''
738 );
739 },
740
741 // Determines whether to add a "not" prefix to the where clause.
742 _not(statement, str) {
743 if (statement.not) return `not ${str}`;
744 return str;
745 },
746
747 _prepInsert(data) {
748 const isRaw = this.formatter.rawOrFn(data);
749 if (isRaw) return isRaw;
750 let columns = [];
751 const values = [];
752 if (!Array.isArray(data)) data = data ? [data] : [];
753 let i = -1;
754 while (++i < data.length) {
755 if (data[i] == null) break;
756 if (i === 0) columns = Object.keys(data[i]).sort();
757 const row = new Array(columns.length);
758 const keys = Object.keys(data[i]);
759 let j = -1;
760 while (++j < keys.length) {
761 const key = keys[j];
762 let idx = columns.indexOf(key);
763 if (idx === -1) {
764 columns = columns.concat(key).sort();
765 idx = columns.indexOf(key);
766 let k = -1;
767 while (++k < values.length) {
768 values[k].splice(idx, 0, undefined);
769 }
770 row.splice(idx, 0, undefined);
771 }
772 row[idx] = data[i][key];
773 }
774 values.push(row);
775 }
776 return {
777 columns,
778 values,
779 };
780 },
781
782 // "Preps" the update.
783 _prepUpdate(data = {}) {
784 const { counter = {} } = this.single;
785
786 for (const column of Object.keys(counter)) {
787 //Skip?
788 if (has(data, column)) {
789 //Needed?
790 this.client.logger.warn(
791 `increment/decrement called for a column that has already been specified in main .update() call. Ignoring increment/decrement and using value from .update() call.`
792 );
793 continue;
794 }
795
796 let value = counter[column];
797
798 const symbol = value < 0 ? '-' : '+';
799
800 if (symbol === '-') {
801 value = -value;
802 }
803
804 data[column] = this.client.raw(`?? ${symbol} ?`, [column, value]);
805 }
806
807 data = omitBy(data, isUndefined);
808
809 const vals = [];
810 const columns = Object.keys(data);
811 let i = -1;
812
813 while (++i < columns.length) {
814 vals.push(
815 this.formatter.wrap(columns[i]) +
816 ' = ' +
817 this.formatter.parameter(data[columns[i]])
818 );
819 }
820
821 if (isEmpty(vals)) {
822 throw new Error(
823 [
824 'Empty .update() call detected!',
825 'Update data does not contain any values to update.',
826 'This will result in a faulty query.',
827 this.single.table ? `Table: ${this.single.table}.` : '',
828 this.single.update
829 ? `Columns: ${Object.keys(this.single.update)}.`
830 : '',
831 ].join(' ')
832 );
833 }
834
835 return vals;
836 },
837
838 _formatGroupsItemValue(value) {
839 const { formatter } = this;
840 if (value instanceof Raw) {
841 return formatter.unwrapRaw(value);
842 } else if (value instanceof QueryBuilder) {
843 return '(' + formatter.columnize(value) + ')';
844 } else {
845 return formatter.columnize(value);
846 }
847 },
848
849 // Compiles the `order by` statements.
850 _groupsOrders(type) {
851 const items = this.grouped[type];
852 if (!items) return '';
853 const { formatter } = this;
854 const sql = items.map((item) => {
855 const column = this._formatGroupsItemValue(item.value);
856 const direction =
857 type === 'order' && item.type !== 'orderByRaw'
858 ? ` ${formatter.direction(item.direction)}`
859 : '';
860 return column + direction;
861 });
862 return sql.length ? type + ' by ' + sql.join(', ') : '';
863 },
864});
865
866QueryCompiler.prototype.first = QueryCompiler.prototype.select;
867
868// Get the table name, wrapping it if necessary.
869// Implemented as a property to prevent ordering issues as described in #704.
870Object.defineProperty(QueryCompiler.prototype, 'tableName', {
871 get() {
872 if (!this._tableName) {
873 // Only call this.formatter.wrap() the first time this property is accessed.
874 let tableName = this.single.table;
875 const schemaName = this.single.schema;
876
877 if (tableName && schemaName) tableName = `${schemaName}.${tableName}`;
878
879 this._tableName = tableName
880 ? // Wrap subQuery with parenthesis, #3485
881 this.formatter.wrap(tableName, tableName instanceof QueryBuilder)
882 : '';
883 }
884 return this._tableName;
885 },
886});
887
888module.exports = QueryCompiler;