@strapi/database 4.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +22 -0
  2. package/examples/connections.js +36 -0
  3. package/examples/data.sqlite +0 -0
  4. package/examples/docker-compose.yml +29 -0
  5. package/examples/index.js +73 -0
  6. package/examples/models.js +341 -0
  7. package/examples/typings.ts +17 -0
  8. package/lib/dialects/dialect.js +45 -0
  9. package/lib/dialects/index.js +28 -0
  10. package/lib/dialects/mysql/index.js +51 -0
  11. package/lib/dialects/mysql/schema-inspector.js +203 -0
  12. package/lib/dialects/postgresql/index.js +49 -0
  13. package/lib/dialects/postgresql/schema-inspector.js +229 -0
  14. package/lib/dialects/sqlite/index.js +74 -0
  15. package/lib/dialects/sqlite/schema-inspector.js +151 -0
  16. package/lib/entity-manager.js +886 -0
  17. package/lib/entity-repository.js +110 -0
  18. package/lib/errors.js +14 -0
  19. package/lib/fields.d.ts +9 -0
  20. package/lib/fields.js +232 -0
  21. package/lib/index.d.ts +146 -0
  22. package/lib/index.js +60 -0
  23. package/lib/lifecycles/index.d.ts +50 -0
  24. package/lib/lifecycles/index.js +66 -0
  25. package/lib/lifecycles/subscribers/index.d.ts +9 -0
  26. package/lib/lifecycles/subscribers/models-lifecycles.js +19 -0
  27. package/lib/lifecycles/subscribers/timestamps.js +65 -0
  28. package/lib/metadata/index.js +219 -0
  29. package/lib/metadata/relations.js +488 -0
  30. package/lib/migrations/index.d.ts +9 -0
  31. package/lib/migrations/index.js +69 -0
  32. package/lib/migrations/storage.js +49 -0
  33. package/lib/query/helpers/index.js +10 -0
  34. package/lib/query/helpers/join.js +95 -0
  35. package/lib/query/helpers/order-by.js +70 -0
  36. package/lib/query/helpers/populate.js +652 -0
  37. package/lib/query/helpers/search.js +84 -0
  38. package/lib/query/helpers/transform.js +84 -0
  39. package/lib/query/helpers/where.js +322 -0
  40. package/lib/query/index.js +7 -0
  41. package/lib/query/query-builder.js +348 -0
  42. package/lib/schema/__tests__/schema-diff.test.js +181 -0
  43. package/lib/schema/builder.js +352 -0
  44. package/lib/schema/diff.js +376 -0
  45. package/lib/schema/index.d.ts +49 -0
  46. package/lib/schema/index.js +95 -0
  47. package/lib/schema/schema.js +209 -0
  48. package/lib/schema/storage.js +75 -0
  49. package/lib/types/index.d.ts +6 -0
  50. package/lib/types/index.js +34 -0
  51. package/lib/utils/content-types.js +41 -0
  52. package/package.json +39 -0
@@ -0,0 +1,488 @@
1
+ /**
2
+ * @module relations
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ const _ = require('lodash/fp');
8
+
9
+ const hasInversedBy = _.has('inversedBy');
10
+ const hasMappedBy = _.has('mappedBy');
11
+
12
+ const isOneToAny = attribute => ['oneToOne', 'oneToMany'].includes(attribute.relation);
13
+ const isBidirectional = attribute => hasInversedBy(attribute) || hasMappedBy(attribute);
14
+ const isOwner = attribute => !isBidirectional(attribute) || hasInversedBy(attribute);
15
+ const shouldUseJoinTable = attribute => attribute.useJoinTable !== false;
16
+
17
+ /**
18
+ * Creates a oneToOne relation metadata
19
+ *
20
+ * if owner then
21
+ * if with join table then
22
+ * create join table
23
+ * else
24
+ * create joinColumn
25
+ * if bidirectional then
26
+ * set inverse attribute joinCol or joinTable info correctly
27
+ * else
28
+ * this property must be set by the owner side
29
+ * verify the owner side is valid // should be done before or at the same time ?
30
+ *
31
+ * @param {string} attributeName
32
+ * @param {Attribute} attribute
33
+ * @param {ModelMetadata} meta
34
+ * @param {Metadata} metadata
35
+ * @retuns void
36
+ */
37
+ const createOneToOne = (attributeName, attribute, meta, metadata) => {
38
+ if (isOwner(attribute)) {
39
+ if (shouldUseJoinTable(attribute)) {
40
+ createJoinTable(metadata, {
41
+ attribute,
42
+ attributeName,
43
+ meta,
44
+ });
45
+ } else {
46
+ createJoinColum(metadata, {
47
+ attribute,
48
+ attributeName,
49
+ meta,
50
+ });
51
+ }
52
+ } else {
53
+ // TODO: verify other side is valid
54
+ }
55
+ };
56
+
57
+ /**
58
+ * Creates a oneToMany relation metadata
59
+ *
60
+ * if unidirectional then
61
+ * create join table
62
+ * if bidirectional then
63
+ * cannot be owning side
64
+ * do nothing
65
+ *
66
+ * @param {string} attributeName
67
+ * @param {Attribute} attribute
68
+ * @param {ModelMetadata} meta
69
+ * @param {Metadata} metadata
70
+ */
71
+ const createOneToMany = (attributeName, attribute, meta, metadata) => {
72
+ if (!isBidirectional(attribute)) {
73
+ createJoinTable(metadata, {
74
+ attribute,
75
+ attributeName,
76
+ meta,
77
+ });
78
+ } else {
79
+ if (isOwner(attribute)) {
80
+ throw new Error(
81
+ 'one side of a oneToMany cannot be the owner side in a bidirectional relation'
82
+ );
83
+ }
84
+ }
85
+ };
86
+
87
+ /**
88
+ * Creates a manyToOne relation metadata
89
+ *
90
+ * if unidirectional then
91
+ * if with join table then
92
+ * create join table
93
+ * else
94
+ * create join column
95
+ * else
96
+ * must be the owner side
97
+ * if with join table then
98
+ * create join table
99
+ * else
100
+ * create join column
101
+ * set inverse attribute joinCol or joinTable info correctly
102
+ *
103
+ * @param {string} attributeName
104
+ * @param {Attribute} attribute
105
+ * @param {ModelMetadata} meta
106
+ * @param {Metadata} metadata
107
+ */
108
+ const createManyToOne = (attributeName, attribute, meta, metadata) => {
109
+ if (isBidirectional(attribute) && !isOwner(attribute)) {
110
+ throw new Error('The many side of a manyToOne must be the owning side');
111
+ }
112
+
113
+ if (shouldUseJoinTable(attribute)) {
114
+ createJoinTable(metadata, {
115
+ attribute,
116
+ attributeName,
117
+ meta,
118
+ });
119
+ } else {
120
+ createJoinColum(metadata, {
121
+ attribute,
122
+ attributeName,
123
+ meta,
124
+ });
125
+ }
126
+ };
127
+
128
+ /**
129
+ * Creates a manyToMany relation metadata
130
+ *
131
+ * if unidirectional
132
+ * create join table
133
+ * else
134
+ * if owner then
135
+ * if with join table then
136
+ * create join table
137
+ * else
138
+ * do nothing
139
+ *
140
+ * @param {string} attributeName
141
+ * @param {Attribute} attribute
142
+ * @param {ModelMetadata} meta
143
+ * @param {Metadata} metadata
144
+ */
145
+ const createManyToMany = (attributeName, attribute, meta, metadata) => {
146
+ if (!isBidirectional(attribute) || isOwner(attribute)) {
147
+ createJoinTable(metadata, {
148
+ attribute,
149
+ attributeName,
150
+ meta,
151
+ });
152
+ }
153
+ };
154
+
155
+ /**
156
+ * Creates a morphToOne relation metadata
157
+ *
158
+ * if with join table then
159
+ * create join table
160
+ * else
161
+ * create join columnsa
162
+ *
163
+ * if bidirectionnal
164
+ * set info in the traget
165
+ *
166
+ *
167
+ * @param {string} attributeName
168
+ * @param {Attribute} attribute
169
+ * @param {ModelMetadata} meta
170
+ * @param {Metadata} metadata
171
+ */
172
+ const createMorphToOne = (attributeName, attribute /*meta, metadata*/) => {
173
+ const idColumnName = 'target_id';
174
+ const typeColumnName = 'target_type';
175
+
176
+ Object.assign(attribute, {
177
+ owner: true,
178
+ morphColumn: {
179
+ // TODO: add referenced column
180
+ typeColumn: {
181
+ name: typeColumnName,
182
+ },
183
+ idColumn: {
184
+ name: idColumnName,
185
+ referencedColumn: 'id',
186
+ },
187
+ },
188
+ });
189
+
190
+ // TODO: implement bidirectional
191
+ };
192
+
193
+ /**
194
+ * Creates a morphToMany relation metadata
195
+ *
196
+ * @param {string} attributeName
197
+ * @param {Attribute} attribute
198
+ * @param {ModelMetadata} meta
199
+ * @param {Metadata} metadata
200
+ */
201
+ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
202
+ const joinTableName = _.snakeCase(`${meta.tableName}_${attributeName}_morphs`);
203
+
204
+ const joinColumnName = _.snakeCase(`${meta.singularName}_id`);
205
+ const morphColumnName = _.snakeCase(`${attributeName}`);
206
+ const idColumnName = `${morphColumnName}_id`;
207
+ const typeColumnName = `${morphColumnName}_type`;
208
+
209
+ metadata.add({
210
+ uid: joinTableName,
211
+ tableName: joinTableName,
212
+ attributes: {
213
+ [joinColumnName]: {
214
+ type: 'integer',
215
+ column: {
216
+ unsigned: true,
217
+ },
218
+ },
219
+ [idColumnName]: {
220
+ type: 'integer',
221
+ column: {
222
+ unsigned: true,
223
+ },
224
+ },
225
+ [typeColumnName]: {
226
+ type: 'string',
227
+ },
228
+ field: {
229
+ type: 'string',
230
+ },
231
+ order: {
232
+ type: 'integer',
233
+ column: {
234
+ unsigned: true,
235
+ },
236
+ },
237
+ },
238
+ indexes: [
239
+ {
240
+ name: `${joinTableName}_fk`,
241
+ columns: [joinColumnName],
242
+ },
243
+ ],
244
+ foreignKeys: [
245
+ {
246
+ name: `${joinTableName}_fk`,
247
+ columns: [joinColumnName],
248
+ referencedColumns: ['id'],
249
+ referencedTable: meta.tableName,
250
+ onDelete: 'CASCADE',
251
+ },
252
+ ],
253
+ });
254
+
255
+ const joinTable = {
256
+ name: joinTableName,
257
+ joinColumn: {
258
+ name: joinColumnName,
259
+ referencedColumn: 'id',
260
+ },
261
+ morphColumn: {
262
+ typeColumn: {
263
+ name: typeColumnName,
264
+ },
265
+ idColumn: {
266
+ name: idColumnName,
267
+ referencedColumn: 'id',
268
+ },
269
+ },
270
+ orderBy: {
271
+ order: 'asc',
272
+ },
273
+ };
274
+
275
+ attribute.joinTable = joinTable;
276
+ };
277
+
278
+ /**
279
+ * Creates a morphOne relation metadata
280
+ *
281
+ * @param {string} attributeName
282
+ * @param {Attribute} attribute
283
+ * @param {ModelMetadata} meta
284
+ * @param {Metadata} metadata
285
+ */
286
+ const createMorphOne = (attributeName, attribute, meta, metadata) => {
287
+ const targetMeta = metadata.get(attribute.target);
288
+
289
+ if (!targetMeta) {
290
+ throw new Error(`Morph target not found. Looking for ${attribute.target}`);
291
+ }
292
+
293
+ if (!_.has(attribute.morphBy, targetMeta.attributes)) {
294
+ throw new Error(`Morph target attribute not found. Looking for ${attribute.morphBy}`);
295
+ }
296
+ };
297
+
298
+ /**
299
+ * Creates a morphMany relation metadata
300
+ *
301
+ * @param {string} attributeName
302
+ * @param {Attribute} attribute
303
+ * @param {ModelMetadata} meta
304
+ * @param {Metadata} metadata
305
+ */
306
+ const createMorphMany = (attributeName, attribute, meta, metadata) => {
307
+ const targetMeta = metadata.get(attribute.target);
308
+
309
+ if (!targetMeta) {
310
+ throw new Error(`Morph target not found. Looking for ${attribute.target}`);
311
+ }
312
+
313
+ if (!_.has(attribute.morphBy, targetMeta.attributes)) {
314
+ throw new Error(`Morph target attribute not found. Looking for ${attribute.morphBy}`);
315
+ }
316
+ };
317
+
318
+ /**
319
+ * Creates a relation metadata
320
+ *
321
+ * @param {string} attributeName
322
+ * @param {Attribute} attribute
323
+ * @param {ModelMetadata} meta
324
+ * @param {Metadata} metadata
325
+ */
326
+ const createRelation = (attributeName, attribute, meta, metadata) => {
327
+ switch (attribute.relation) {
328
+ case 'oneToOne':
329
+ return createOneToOne(attributeName, attribute, meta, metadata);
330
+ case 'oneToMany':
331
+ return createOneToMany(attributeName, attribute, meta, metadata);
332
+ case 'manyToOne':
333
+ return createManyToOne(attributeName, attribute, meta, metadata);
334
+ case 'manyToMany':
335
+ return createManyToMany(attributeName, attribute, meta, metadata);
336
+ case 'morphToOne':
337
+ return createMorphToOne(attributeName, attribute, meta, metadata);
338
+ case 'morphToMany':
339
+ return createMorphToMany(attributeName, attribute, meta, metadata);
340
+ case 'morphOne':
341
+ return createMorphOne(attributeName, attribute, meta, metadata);
342
+ case 'morphMany':
343
+ return createMorphMany(attributeName, attribute, meta, metadata);
344
+ }
345
+
346
+ throw new Error(`Unknown relation ${attribute.relation}`);
347
+ };
348
+
349
+ /**
350
+ * Creates a join column info and add them to the attribute meta
351
+ * @param {Object} metadata metadata registry
352
+ * @param {Object} param
353
+ * @param {Object} param.attribute associated attribute
354
+ * @param {string} param.attributeName name of the associated attribute
355
+ * @param {Object} param.meta model metadata
356
+ */
357
+ const createJoinColum = (metadata, { attribute, attributeName /*meta */ }) => {
358
+ const targetMeta = metadata.get(attribute.target);
359
+
360
+ const joinColumnName = _.snakeCase(`${attributeName}_id`);
361
+ const joinColumn = {
362
+ name: joinColumnName,
363
+ referencedColumn: 'id',
364
+ referencedTable: targetMeta.tableName,
365
+ };
366
+
367
+ Object.assign(attribute, { owner: true, joinColumn });
368
+
369
+ if (isBidirectional(attribute)) {
370
+ const inverseAttribute = targetMeta.attributes[attribute.inversedBy];
371
+
372
+ Object.assign(inverseAttribute, {
373
+ joinColumn: {
374
+ name: joinColumn.referencedColumn,
375
+ referencedColumn: joinColumn.name,
376
+ },
377
+ });
378
+ }
379
+ };
380
+
381
+ /**
382
+ * Creates a join table and add it to the attribute meta
383
+ * @param {Object} metadata metadata registry
384
+ * @param {Object} param
385
+ * @param {Object} param.attribute associated attribute
386
+ * @param {string} param.attributeName name of the associated attribute
387
+ * @param {Object} param.meta model metadata
388
+ */
389
+ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
390
+ const targetMeta = metadata.get(attribute.target);
391
+
392
+ if (!targetMeta) {
393
+ throw new Error(`Unknow target ${attribute.target}`);
394
+ }
395
+
396
+ const joinTableName = _.snakeCase(`${meta.tableName}_${attributeName}_links`);
397
+
398
+ let joinColumnName = _.snakeCase(`${meta.singularName}_id`);
399
+ let inverseJoinColumnName = _.snakeCase(`${targetMeta.singularName}_id`);
400
+
401
+ // if relation is slef referencing
402
+ if (joinColumnName === inverseJoinColumnName) {
403
+ inverseJoinColumnName = `inv_${inverseJoinColumnName}`;
404
+ }
405
+
406
+ metadata.add({
407
+ uid: joinTableName,
408
+ tableName: joinTableName,
409
+ attributes: {
410
+ [joinColumnName]: {
411
+ type: 'integer',
412
+ column: {
413
+ unsigned: true,
414
+ },
415
+ },
416
+ [inverseJoinColumnName]: {
417
+ type: 'integer',
418
+ column: {
419
+ unsigned: true,
420
+ },
421
+ },
422
+ // TODO: add extra pivot attributes -> user should use an intermediate entity
423
+ },
424
+ indexes: [
425
+ {
426
+ name: `${joinTableName}_fk`,
427
+ columns: [joinColumnName],
428
+ },
429
+ {
430
+ name: `${joinTableName}_inv_fk`,
431
+ columns: [inverseJoinColumnName],
432
+ },
433
+ ],
434
+ foreignKeys: [
435
+ {
436
+ name: `${joinTableName}_fk`,
437
+ columns: [joinColumnName],
438
+ referencedColumns: ['id'],
439
+ referencedTable: meta.tableName,
440
+ onDelete: 'CASCADE',
441
+ },
442
+ {
443
+ name: `${joinTableName}_inv_fk`,
444
+ columns: [inverseJoinColumnName],
445
+ referencedColumns: ['id'],
446
+ referencedTable: targetMeta.tableName,
447
+ onDelete: 'CASCADE',
448
+ },
449
+ ],
450
+ });
451
+
452
+ const joinTable = {
453
+ name: joinTableName,
454
+ joinColumn: {
455
+ name: joinColumnName,
456
+ referencedColumn: 'id',
457
+ },
458
+ inverseJoinColumn: {
459
+ name: inverseJoinColumnName,
460
+ referencedColumn: 'id',
461
+ },
462
+ };
463
+
464
+ attribute.joinTable = joinTable;
465
+
466
+ if (isBidirectional(attribute)) {
467
+ const inverseAttribute = targetMeta.attributes[attribute.inversedBy];
468
+
469
+ if (!inverseAttribute) {
470
+ throw new Error(
471
+ `inversedBy attribute ${attribute.inversedBy} not found target ${targetMeta.uid}`
472
+ );
473
+ }
474
+
475
+ inverseAttribute.joinTable = {
476
+ name: joinTableName,
477
+ joinColumn: joinTable.inverseJoinColumn,
478
+ inverseJoinColumn: joinTable.joinColumn,
479
+ };
480
+ }
481
+ };
482
+
483
+ module.exports = {
484
+ createRelation,
485
+
486
+ isBidirectional,
487
+ isOneToAny,
488
+ };
@@ -0,0 +1,9 @@
1
+ import { Database } from '../';
2
+
3
+ export interface MigrationProvider {
4
+ shouldRun(): Promise<boolean>;
5
+ up(): Promise<void>;
6
+ down(): Promise<void>;
7
+ }
8
+
9
+ export function createMigrationsProvider(db: Database): MigrationProvider;
@@ -0,0 +1,69 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fse = require('fs-extra');
5
+ const Umzug = require('umzug');
6
+
7
+ const createStorage = require('./storage');
8
+
9
+ // TODO: check multiple commands in one sql statement
10
+ const migrationResolver = path => {
11
+ // if sql file run with knex raw
12
+ if (path.match(/\.sql$/)) {
13
+ const sql = fse.readFileSync(path, 'utf8');
14
+
15
+ return {
16
+ up: knex => knex.raw(sql),
17
+ down() {},
18
+ };
19
+ }
20
+
21
+ // NOTE: we can add some ts register if we want to handle ts migration files at some point
22
+ return require(path);
23
+ };
24
+
25
+ const createUmzugProvider = db => {
26
+ const migrationDir = path.join(strapi.dirs.root, 'database/migrations');
27
+
28
+ fse.ensureDirSync(migrationDir);
29
+
30
+ const wrapFn = fn => db => db.connection.transaction(trx => Promise.resolve(fn(trx)));
31
+ const storage = createStorage({ db, tableName: 'strapi_migrations' });
32
+
33
+ return new Umzug({
34
+ storage,
35
+ migrations: {
36
+ path: migrationDir,
37
+ pattern: /\.(js|sql)$/,
38
+ params: [db],
39
+ wrap: wrapFn,
40
+ customResolver: migrationResolver,
41
+ },
42
+ });
43
+ };
44
+
45
+ // NOTE: when needed => add internal migrations for core & plugins. How do we overlap them with users migrations ?
46
+
47
+ /**
48
+ * Creates migrations provider
49
+ * @type {import('.').createMigrationsProvider}
50
+ */
51
+ const createMigrationsProvider = db => {
52
+ const migrations = createUmzugProvider(db);
53
+
54
+ return {
55
+ async shouldRun() {
56
+ const pending = await migrations.pending();
57
+
58
+ return pending.length > 0;
59
+ },
60
+ async up() {
61
+ await migrations.up();
62
+ },
63
+ async down() {
64
+ await migrations.down();
65
+ },
66
+ };
67
+ };
68
+
69
+ module.exports = { createMigrationsProvider };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const createStorage = (opts = {}) => {
4
+ const tableName = opts.tableName || 'strapi_migrations';
5
+ const knex = opts.db.connection;
6
+
7
+ const hasMigrationTable = () => knex.schema.hasTable(tableName);
8
+
9
+ const createMigrationTable = () => {
10
+ return knex.schema.createTable(tableName, table => {
11
+ table.increments('id');
12
+ table.string('name');
13
+ table.datetime('time', { useTz: false });
14
+ });
15
+ };
16
+
17
+ return {
18
+ async logMigration(migrationName) {
19
+ await knex
20
+ .insert({
21
+ name: migrationName,
22
+ time: new Date(),
23
+ })
24
+ .into(tableName);
25
+ },
26
+
27
+ async unlogMigration(migrationName) {
28
+ await knex(tableName)
29
+ .del()
30
+ .where({ name: migrationName });
31
+ },
32
+
33
+ async executed() {
34
+ if (!(await hasMigrationTable())) {
35
+ await createMigrationTable();
36
+ return [];
37
+ }
38
+
39
+ const logs = await knex
40
+ .select()
41
+ .from(tableName)
42
+ .orderBy('time');
43
+
44
+ return logs.map(log => log.name);
45
+ },
46
+ };
47
+ };
48
+
49
+ module.exports = createStorage;
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ ...require('./search'),
5
+ ...require('./order-by'),
6
+ ...require('./join'),
7
+ ...require('./populate'),
8
+ ...require('./where'),
9
+ ...require('./transform'),
10
+ };
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ const createPivotJoin = (qb, joinTable, alias, tragetMeta) => {
4
+ const joinAlias = qb.getAlias();
5
+ qb.join({
6
+ alias: joinAlias,
7
+ referencedTable: joinTable.name,
8
+ referencedColumn: joinTable.joinColumn.name,
9
+ rootColumn: joinTable.joinColumn.referencedColumn,
10
+ rootTable: alias,
11
+ on: joinTable.on,
12
+ });
13
+
14
+ const subAlias = qb.getAlias();
15
+ qb.join({
16
+ alias: subAlias,
17
+ referencedTable: tragetMeta.tableName,
18
+ referencedColumn: joinTable.inverseJoinColumn.referencedColumn,
19
+ rootColumn: joinTable.inverseJoinColumn.name,
20
+ rootTable: joinAlias,
21
+ });
22
+
23
+ return subAlias;
24
+ };
25
+
26
+ const createJoin = (ctx, { alias, attributeName, attribute }) => {
27
+ const { db, qb } = ctx;
28
+
29
+ if (attribute.type !== 'relation') {
30
+ throw new Error(`Cannot join on non relational field ${attributeName}`);
31
+ }
32
+
33
+ const tragetMeta = db.metadata.get(attribute.target);
34
+
35
+ const joinColumn = attribute.joinColumn;
36
+
37
+ if (joinColumn) {
38
+ const subAlias = qb.getAlias();
39
+ qb.join({
40
+ alias: subAlias,
41
+ referencedTable: tragetMeta.tableName,
42
+ referencedColumn: joinColumn.referencedColumn,
43
+ rootColumn: joinColumn.name,
44
+ rootTable: alias,
45
+ });
46
+ return subAlias;
47
+ }
48
+
49
+ const joinTable = attribute.joinTable;
50
+ if (joinTable) {
51
+ return createPivotJoin(qb, joinTable, alias, tragetMeta);
52
+ }
53
+
54
+ return alias;
55
+ };
56
+
57
+ // TODO: toColumnName for orderBy & on
58
+ const applyJoin = (qb, join) => {
59
+ const {
60
+ method = 'leftJoin',
61
+ alias,
62
+ referencedTable,
63
+ referencedColumn,
64
+ rootColumn,
65
+ rootTable = qb.alias,
66
+ on,
67
+ orderBy,
68
+ } = join;
69
+
70
+ qb[method]({ [alias]: referencedTable }, inner => {
71
+ inner.on(`${rootTable}.${rootColumn}`, `${alias}.${referencedColumn}`);
72
+
73
+ if (on) {
74
+ for (const key in on) {
75
+ inner.onVal(`${alias}.${key}`, on[key]);
76
+ }
77
+ }
78
+ });
79
+
80
+ if (orderBy) {
81
+ Object.keys(orderBy).forEach(column => {
82
+ const direction = orderBy[column];
83
+ qb.orderBy(`${alias}.${column}`, direction);
84
+ });
85
+ }
86
+ };
87
+
88
+ const applyJoins = (qb, joins) => joins.forEach(join => applyJoin(qb, join));
89
+
90
+ module.exports = {
91
+ createJoin,
92
+ createPivotJoin,
93
+ applyJoins,
94
+ applyJoin,
95
+ };