QueryBuilderOperationSupport.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. const QueryBuilderContextBase = require('./QueryBuilderContextBase');
  2. const QueryBuilderUserContext = require('./QueryBuilderUserContext');
  3. class QueryBuilderOperationSupport {
  4. constructor(knex) {
  5. this._knex = knex;
  6. this._operations = [];
  7. this._context = new this.constructor.QueryBuilderContext(this);
  8. this._parentQuery = null;
  9. }
  10. static get QueryBuilderContext() {
  11. return QueryBuilderContextBase;
  12. }
  13. static get QueryBuilderUserContext() {
  14. return QueryBuilderUserContext;
  15. }
  16. context(obj) {
  17. const ctx = this._context;
  18. if (arguments.length === 0) {
  19. return ctx.userContext;
  20. } else {
  21. ctx.userContext = ctx.userContext.newFromObject(this, obj);
  22. return this;
  23. }
  24. }
  25. mergeContext(obj) {
  26. const ctx = this._context;
  27. ctx.userContext = ctx.userContext.newMerge(this, obj);
  28. return this;
  29. }
  30. internalContext(ctx) {
  31. if (arguments.length === 0) {
  32. return this._context;
  33. } else {
  34. this._context = ctx;
  35. return this;
  36. }
  37. }
  38. internalOptions(opt) {
  39. if (arguments.length === 0) {
  40. return this._context.options;
  41. } else {
  42. const oldOpt = this._context.options;
  43. this._context.options = Object.assign(oldOpt, opt);
  44. return this;
  45. }
  46. }
  47. childQueryOf(query, fork) {
  48. if (query) {
  49. let ctx = query.internalContext();
  50. if (fork) {
  51. ctx = ctx.clone();
  52. }
  53. this._parentQuery = query;
  54. this.internalContext(ctx);
  55. if (this.unsafeKnex() === null) {
  56. this.knex(query.unsafeKnex());
  57. }
  58. }
  59. return this;
  60. }
  61. subqueryOf(query) {
  62. if (query) {
  63. this._parentQuery = query;
  64. this.knex(query.knex());
  65. }
  66. return this;
  67. }
  68. parentQuery() {
  69. return this._parentQuery;
  70. }
  71. knex() {
  72. if (arguments.length === 0) {
  73. const knex = this.unsafeKnex();
  74. if (!knex) {
  75. throw new Error(
  76. `no database connection available for a query. You need to bind the model class or the query to a knex instance.`
  77. );
  78. }
  79. return knex;
  80. } else {
  81. this._knex = arguments[0];
  82. return this;
  83. }
  84. }
  85. unsafeKnex() {
  86. return this._context.knex || this._knex || null;
  87. }
  88. clear(operationSelector) {
  89. const operations = [];
  90. this.forEachOperation(
  91. operationSelector,
  92. op => {
  93. operations.push(op);
  94. },
  95. false
  96. );
  97. this._operations = operations;
  98. return this;
  99. }
  100. copyFrom(queryBuilder, operationSelector) {
  101. const operations = this._operations;
  102. queryBuilder.forEachOperation(operationSelector, op => {
  103. operations.push(op);
  104. });
  105. return this;
  106. }
  107. has(operationSelector) {
  108. return this.indexOfOperation(operationSelector) !== -1;
  109. }
  110. indexOfOperation(operationSelector) {
  111. let idx = -1;
  112. this.forEachOperation(operationSelector, (op, i) => {
  113. idx = i;
  114. return false;
  115. });
  116. return idx;
  117. }
  118. indexOfLastOperation(operationSelector) {
  119. let idx = -1;
  120. this.forEachOperation(operationSelector, (op, i) => {
  121. idx = i;
  122. });
  123. return idx;
  124. }
  125. findLastOperation(operationSelector) {
  126. const idx = this.indexOfLastOperation(operationSelector);
  127. if (idx !== -1) {
  128. return this._operations[idx];
  129. } else {
  130. return null;
  131. }
  132. }
  133. forEachOperation(operationSelector, callback, match) {
  134. match = match == null ? true : match;
  135. let check;
  136. if (operationSelector instanceof RegExp) {
  137. check = op => operationSelector.test(op.name);
  138. } else if (typeof operationSelector === 'string') {
  139. check = op => op.name === operationSelector;
  140. } else {
  141. check = op => op.is(operationSelector);
  142. }
  143. for (let i = 0, l = this._operations.length; i < l; ++i) {
  144. const op = this._operations[i];
  145. if (check(op) === match) {
  146. if (callback(op, i) === false) {
  147. break;
  148. }
  149. }
  150. }
  151. return this;
  152. }
  153. addOperation(operation, args) {
  154. const shouldAdd = operation.onAdd(this, args);
  155. if (shouldAdd) {
  156. this._operations.push(operation);
  157. }
  158. return this;
  159. }
  160. addOperationToFront(operation, args) {
  161. const shouldAdd = operation.onAdd(this, args);
  162. if (shouldAdd) {
  163. this._operations.unshift(operation);
  164. }
  165. return this;
  166. }
  167. clone() {
  168. return this.baseCloneInto(new this.constructor(this.unsafeKnex()));
  169. }
  170. baseCloneInto(builder) {
  171. builder._knex = this._knex;
  172. builder._operations = this._operations.slice();
  173. builder._context = this._context.clone();
  174. builder._parentQuery = this._parentQuery;
  175. return builder;
  176. }
  177. build() {
  178. return this.buildInto(this.knex().queryBuilder());
  179. }
  180. buildInto(knexBuilder) {
  181. this.executeOnBuild();
  182. // onBuildKnex operations should never add new operations. They should only call
  183. // methods on the knex query builder.
  184. for (let i = 0, l = this._operations.length; i < l; ++i) {
  185. this._operations[i].onBuildKnex(knexBuilder, this);
  186. if (this._operations.length !== l) {
  187. throw new Error('onBuildKnex should only call query building methods on the knex builder');
  188. }
  189. }
  190. return knexBuilder;
  191. }
  192. executeOnBuild() {
  193. const operationSet = new Set();
  194. for (let i = 0; i < this._operations.length; ++i) {
  195. operationSet.add(this._operations[i]);
  196. }
  197. let i = 0;
  198. while (i < this._operations.length) {
  199. const iOp = this._operations[i];
  200. const newOps = [];
  201. iOp.onBuild(this);
  202. for (let j = 0; j < this._operations.length; ++j) {
  203. const jOp = this._operations[j];
  204. // onBuild may have removed operations before `op`.
  205. // We need to update the index.
  206. if (iOp === jOp) {
  207. i = j;
  208. }
  209. if (!operationSet.has(jOp)) {
  210. // New operation was added.
  211. newOps.push(jOp);
  212. operationSet.add(jOp);
  213. }
  214. }
  215. if (newOps.length !== 0) {
  216. // Make room for the new operations after the current operation.
  217. for (let j = this._operations.length - 1; j > i + newOps.length; --j) {
  218. this._operations[j] = this._operations[j - newOps.length];
  219. }
  220. // Move the new operations after the current operation.
  221. for (let j = 0; j < newOps.length; ++j) {
  222. this._operations[i + j + 1] = newOps[j];
  223. }
  224. }
  225. ++i;
  226. }
  227. }
  228. toString() {
  229. return this.build().toString();
  230. }
  231. toSql() {
  232. return this.toString();
  233. }
  234. skipUndefined() {
  235. this._context.options.skipUndefined = true;
  236. return this;
  237. }
  238. }
  239. module.exports = QueryBuilderOperationSupport;