@strapi/database 4.0.0-next.9 → 4.0.3

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 (46) hide show
  1. package/jest.config.js +10 -0
  2. package/lib/dialects/dialect.js +45 -0
  3. package/lib/dialects/index.js +7 -113
  4. package/lib/dialects/mysql/index.js +51 -0
  5. package/lib/dialects/mysql/schema-inspector.js +199 -0
  6. package/lib/dialects/postgresql/index.js +49 -0
  7. package/lib/dialects/postgresql/schema-inspector.js +232 -0
  8. package/lib/dialects/sqlite/index.js +73 -0
  9. package/lib/dialects/sqlite/schema-inspector.js +151 -0
  10. package/lib/entity-manager.js +18 -14
  11. package/lib/entity-repository.js +2 -3
  12. package/lib/errors.js +44 -2
  13. package/lib/fields.d.ts +2 -3
  14. package/lib/fields.js +7 -16
  15. package/lib/index.d.ts +53 -8
  16. package/lib/index.js +44 -27
  17. package/lib/lifecycles/index.d.ts +50 -0
  18. package/lib/{lifecycles.js → lifecycles/index.js} +25 -14
  19. package/lib/lifecycles/subscribers/index.d.ts +9 -0
  20. package/lib/lifecycles/subscribers/models-lifecycles.js +19 -0
  21. package/lib/lifecycles/subscribers/timestamps.js +65 -0
  22. package/lib/metadata/index.js +84 -95
  23. package/lib/metadata/relations.js +17 -1
  24. package/lib/migrations/index.d.ts +9 -0
  25. package/lib/migrations/index.js +69 -0
  26. package/lib/migrations/storage.js +51 -0
  27. package/lib/query/helpers/join.js +3 -5
  28. package/lib/query/helpers/order-by.js +21 -11
  29. package/lib/query/helpers/populate.js +35 -10
  30. package/lib/query/helpers/search.js +26 -12
  31. package/lib/query/helpers/transform.js +42 -14
  32. package/lib/query/helpers/where.js +92 -57
  33. package/lib/query/query-builder.js +116 -34
  34. package/lib/schema/__tests__/schema-diff.test.js +14 -1
  35. package/lib/schema/builder.js +315 -284
  36. package/lib/schema/diff.js +374 -0
  37. package/lib/schema/index.d.ts +49 -0
  38. package/lib/schema/index.js +47 -50
  39. package/lib/schema/schema.js +22 -27
  40. package/lib/schema/storage.js +79 -0
  41. package/lib/utils/content-types.js +0 -1
  42. package/package.json +27 -21
  43. package/examples/data.sqlite +0 -0
  44. package/lib/configuration.js +0 -49
  45. package/lib/schema/schema-diff.js +0 -337
  46. package/lib/schema/schema-storage.js +0 -44
@@ -1,8 +1,3 @@
1
- /**
2
- * @module metadata
3
- *
4
- */
5
-
6
1
  'use strict';
7
2
 
8
3
  const _ = require('lodash/fp');
@@ -16,43 +11,13 @@ class Metadata extends Map {
16
11
  }
17
12
  }
18
13
 
14
+ // TODO: check if there isn't an attribute with an id already
19
15
  /**
20
16
  * Create Metadata from models configurations
21
- *
22
- * timestamps => not optional anymore but auto added. Auto added on the content type or in the db layer ?
23
- *
24
- * options => options are handled on the layer above. Options convert to fields on the CT
25
- *
26
- * filters => not in v1
27
- *
28
- * attributes
29
- *
30
- * - type
31
- * - mapping field name - column name
32
- * - mapping field type - column type
33
- * - formatter / parser => coming from field type so no
34
- * - indexes / checks / contstraints
35
- * - relations => reference to the target model (function or string to avoid circular deps ?)
36
- * - name of the LEFT/RIGHT side foreign keys
37
- * - name of join table
38
- *
39
- * - compo/dz => reference to the components
40
- * - validators
41
- * - hooks
42
- * - default value
43
- * - required -> should add a not null option instead of the API required
44
- * - unique -> should add a DB unique option instead of the unique in the API (Unique by locale or something else for example)
45
- *
46
- * lifecycles
47
- *
48
- * private fields ? => handled on a different layer
49
17
  * @param {object[]} models
50
18
  * @returns {Metadata}
51
19
  */
52
20
  const createMetadata = (models = []) => {
53
- // TODO: reorder to make sure we can create everything or delete everything in the right order
54
- // TODO: allow passing the join config in the attribute
55
- // TODO: allow passing column config in the attribute
56
21
  const metadata = new Metadata();
57
22
 
58
23
  // init pass
@@ -62,7 +27,6 @@ const createMetadata = (models = []) => {
62
27
  uid: model.uid,
63
28
  tableName: model.tableName,
64
29
  attributes: {
65
- // TODO: check if there isn't an attribute with an id already
66
30
  id: {
67
31
  type: 'increments',
68
32
  },
@@ -83,67 +47,12 @@ const createMetadata = (models = []) => {
83
47
  for (const [attributeName, attribute] of Object.entries(meta.attributes)) {
84
48
  try {
85
49
  if (types.isComponent(attribute.type)) {
86
- // convert component to relation
87
-
88
- Object.assign(attribute, {
89
- type: 'relation',
90
- relation: attribute.repeatable === true ? 'oneToMany' : 'oneToOne',
91
- target: attribute.component,
92
- joinTable: {
93
- name: meta.componentLink.tableName,
94
- joinColumn: {
95
- name: 'entity_id',
96
- referencedColumn: 'id',
97
- },
98
- inverseJoinColumn: {
99
- name: 'component_id',
100
- referencedColumn: 'id',
101
- },
102
- on: {
103
- field: attributeName,
104
- },
105
- orderBy: {
106
- order: 'asc',
107
- },
108
- },
109
- });
110
-
50
+ createComponent(attributeName, attribute, meta, metadata);
111
51
  continue;
112
52
  }
113
53
 
114
54
  if (types.isDynamicZone(attribute.type)) {
115
- //
116
-
117
- Object.assign(attribute, {
118
- type: 'relation',
119
- relation: 'morphToMany',
120
- // TODO: handle restrictions at some point
121
- // target: attribute.components,
122
- joinTable: {
123
- name: meta.componentLink.tableName,
124
- joinColumn: {
125
- name: 'entity_id',
126
- referencedColumn: 'id',
127
- },
128
- morphColumn: {
129
- idColumn: {
130
- name: 'component_id',
131
- referencedColumn: 'id',
132
- },
133
- typeColumn: {
134
- name: 'component_type',
135
- },
136
- typeField: '__component',
137
- },
138
- on: {
139
- field: attributeName,
140
- },
141
- orderBy: {
142
- order: 'asc',
143
- },
144
- },
145
- });
146
-
55
+ createDynamicZone(attributeName, attribute, meta, metadata);
147
56
  continue;
148
57
  }
149
58
 
@@ -151,7 +60,10 @@ const createMetadata = (models = []) => {
151
60
  createRelation(attributeName, attribute, meta, metadata);
152
61
  continue;
153
62
  }
63
+
64
+ createAttribute(attributeName, attribute, meta, metadata);
154
65
  } catch (error) {
66
+ console.log(error);
155
67
  throw new Error(
156
68
  `Error on attribute ${attributeName} in model ${meta.singularName}(${meta.uid}): ${error.message}`
157
69
  );
@@ -159,6 +71,15 @@ const createMetadata = (models = []) => {
159
71
  }
160
72
  }
161
73
 
74
+ for (const meta of metadata.values()) {
75
+ const columnToAttribute = Object.keys(meta.attributes).reduce((acc, key) => {
76
+ const attribute = meta.attributes[key];
77
+ return Object.assign(acc, { [attribute.columnName || key]: key });
78
+ }, {});
79
+
80
+ meta.columnToAttribute = columnToAttribute;
81
+ }
82
+
162
83
  return metadata;
163
84
  };
164
85
 
@@ -201,7 +122,7 @@ const createCompoLinkModelMeta = baseModelMeta => {
201
122
  type: 'integer',
202
123
  column: {
203
124
  unsigned: true,
204
- defaultTo: [0],
125
+ defaultTo: 0,
205
126
  },
206
127
  },
207
128
  },
@@ -209,10 +130,16 @@ const createCompoLinkModelMeta = baseModelMeta => {
209
130
  {
210
131
  name: `${baseModelMeta.tableName}_field_index`,
211
132
  columns: ['field'],
133
+ type: null,
212
134
  },
213
135
  {
214
136
  name: `${baseModelMeta.tableName}_component_type_index`,
215
137
  columns: ['component_type'],
138
+ type: null,
139
+ },
140
+ {
141
+ name: `${baseModelMeta.tableName}_entity_fk`,
142
+ columns: ['entity_id'],
216
143
  },
217
144
  ],
218
145
  foreignKeys: [
@@ -227,4 +154,66 @@ const createCompoLinkModelMeta = baseModelMeta => {
227
154
  };
228
155
  };
229
156
 
157
+ const createDynamicZone = (attributeName, attribute, meta) => {
158
+ Object.assign(attribute, {
159
+ type: 'relation',
160
+ relation: 'morphToMany',
161
+ // TODO: handle restrictions at some point
162
+ // target: attribute.components,
163
+ joinTable: {
164
+ name: meta.componentLink.tableName,
165
+ joinColumn: {
166
+ name: 'entity_id',
167
+ referencedColumn: 'id',
168
+ },
169
+ morphColumn: {
170
+ idColumn: {
171
+ name: 'component_id',
172
+ referencedColumn: 'id',
173
+ },
174
+ typeColumn: {
175
+ name: 'component_type',
176
+ },
177
+ typeField: '__component',
178
+ },
179
+ on: {
180
+ field: attributeName,
181
+ },
182
+ orderBy: {
183
+ order: 'asc',
184
+ },
185
+ },
186
+ });
187
+ };
188
+
189
+ const createComponent = (attributeName, attribute, meta) => {
190
+ Object.assign(attribute, {
191
+ type: 'relation',
192
+ relation: attribute.repeatable === true ? 'oneToMany' : 'oneToOne',
193
+ target: attribute.component,
194
+ joinTable: {
195
+ name: meta.componentLink.tableName,
196
+ joinColumn: {
197
+ name: 'entity_id',
198
+ referencedColumn: 'id',
199
+ },
200
+ inverseJoinColumn: {
201
+ name: 'component_id',
202
+ referencedColumn: 'id',
203
+ },
204
+ on: {
205
+ field: attributeName,
206
+ },
207
+ orderBy: {
208
+ order: 'asc',
209
+ },
210
+ },
211
+ });
212
+ };
213
+
214
+ const createAttribute = (attributeName, attribute) => {
215
+ const columnName = _.snakeCase(attributeName);
216
+ Object.assign(attribute, { columnName });
217
+ };
218
+
230
219
  module.exports = createMetadata;
@@ -235,6 +235,12 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
235
235
  },
236
236
  },
237
237
  },
238
+ indexes: [
239
+ {
240
+ name: `${joinTableName}_fk`,
241
+ columns: [joinColumnName],
242
+ },
243
+ ],
238
244
  foreignKeys: [
239
245
  {
240
246
  name: `${joinTableName}_fk`,
@@ -384,7 +390,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
384
390
  const targetMeta = metadata.get(attribute.target);
385
391
 
386
392
  if (!targetMeta) {
387
- throw new Error(`Unknow target ${attribute.target}`);
393
+ throw new Error(`Unknown target ${attribute.target}`);
388
394
  }
389
395
 
390
396
  const joinTableName = _.snakeCase(`${meta.tableName}_${attributeName}_links`);
@@ -415,6 +421,16 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
415
421
  },
416
422
  // TODO: add extra pivot attributes -> user should use an intermediate entity
417
423
  },
424
+ indexes: [
425
+ {
426
+ name: `${joinTableName}_fk`,
427
+ columns: [joinColumnName],
428
+ },
429
+ {
430
+ name: `${joinTableName}_inv_fk`,
431
+ columns: [inverseJoinColumnName],
432
+ },
433
+ ],
418
434
  foreignKeys: [
419
435
  {
420
436
  name: `${joinTableName}_fk`,
@@ -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.getConnection().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,51 @@
1
+ 'use strict';
2
+
3
+ const createStorage = (opts = {}) => {
4
+ const { db, tableName = 'strapi_migrations' } = opts;
5
+
6
+ const hasMigrationTable = () => db.getSchemaConnection().hasTable(tableName);
7
+
8
+ const createMigrationTable = () => {
9
+ return db.getSchemaConnection().createTable(tableName, table => {
10
+ table.increments('id');
11
+ table.string('name');
12
+ table.datetime('time', { useTz: false });
13
+ });
14
+ };
15
+
16
+ return {
17
+ async logMigration(migrationName) {
18
+ await db
19
+ .getConnection()
20
+ .insert({
21
+ name: migrationName,
22
+ time: new Date(),
23
+ })
24
+ .into(tableName);
25
+ },
26
+
27
+ async unlogMigration(migrationName) {
28
+ await db
29
+ .getConnection(tableName)
30
+ .del()
31
+ .where({ name: migrationName });
32
+ },
33
+
34
+ async executed() {
35
+ if (!(await hasMigrationTable())) {
36
+ await createMigrationTable();
37
+ return [];
38
+ }
39
+
40
+ const logs = await db
41
+ .getConnection(tableName)
42
+ .select()
43
+ .from(tableName)
44
+ .orderBy('time');
45
+
46
+ return logs.map(log => log.name);
47
+ },
48
+ };
49
+ };
50
+
51
+ module.exports = createStorage;
@@ -51,12 +51,10 @@ const createJoin = (ctx, { alias, attributeName, attribute }) => {
51
51
  return createPivotJoin(qb, joinTable, alias, tragetMeta);
52
52
  }
53
53
 
54
- // TODO: polymorphic relations
55
-
56
54
  return alias;
57
55
  };
58
56
 
59
- // TODO: allow for more conditions
57
+ // TODO: toColumnName for orderBy & on
60
58
  const applyJoin = (qb, join) => {
61
59
  const {
62
60
  method = 'leftJoin',
@@ -64,12 +62,12 @@ const applyJoin = (qb, join) => {
64
62
  referencedTable,
65
63
  referencedColumn,
66
64
  rootColumn,
67
- rootTable = this.alias,
65
+ rootTable = qb.alias,
68
66
  on,
69
67
  orderBy,
70
68
  } = join;
71
69
 
72
- qb[method]({ [alias]: referencedTable }, inner => {
70
+ qb[method](`${referencedTable} as ${alias}`, inner => {
73
71
  inner.on(`${rootTable}.${rootColumn}`, `${alias}.${referencedColumn}`);
74
72
 
75
73
  if (on) {
@@ -4,19 +4,23 @@ const _ = require('lodash/fp');
4
4
 
5
5
  const types = require('../../types');
6
6
  const { createJoin } = require('./join');
7
+ const { toColumnName } = require('./transform');
7
8
 
8
- // TODO: convert field names to columns names
9
9
  const processOrderBy = (orderBy, ctx) => {
10
- const { db, uid, qb, alias = qb.alias } = ctx;
10
+ const { db, uid, qb, alias } = ctx;
11
+ const meta = db.metadata.get(uid);
12
+ const { attributes } = meta;
11
13
 
12
14
  if (typeof orderBy === 'string') {
13
- const attribute = db.metadata.get(uid).attributes[orderBy];
15
+ const attribute = attributes[orderBy];
14
16
 
15
17
  if (!attribute) {
16
18
  throw new Error(`Attribute ${orderBy} not found on model ${uid}`);
17
19
  }
18
20
 
19
- return [{ column: `${alias}.${orderBy}` }];
21
+ const columnName = toColumnName(meta, orderBy);
22
+
23
+ return [{ column: qb.aliasColumn(columnName, alias) }];
20
24
  }
21
25
 
22
26
  if (Array.isArray(orderBy)) {
@@ -26,15 +30,25 @@ const processOrderBy = (orderBy, ctx) => {
26
30
  if (_.isPlainObject(orderBy)) {
27
31
  return Object.entries(orderBy).flatMap(([key, direction]) => {
28
32
  const value = orderBy[key];
29
- const attribute = db.metadata.get(uid).attributes[key];
33
+ const attribute = attributes[key];
30
34
 
31
35
  if (!attribute) {
32
36
  throw new Error(`Attribute ${key} not found on model ${uid}`);
33
37
  }
34
38
 
39
+ if (types.isScalar(attribute.type)) {
40
+ const columnName = toColumnName(meta, key);
41
+
42
+ return { column: qb.aliasColumn(columnName, alias), order: direction };
43
+ }
44
+
35
45
  if (attribute.type === 'relation') {
36
- // TODO: pass down some filters (e.g published at)
37
- const subAlias = createJoin(ctx, { alias, uid, attributeName: key, attribute });
46
+ const subAlias = createJoin(ctx, {
47
+ alias: alias || qb.alias,
48
+ uid,
49
+ attributeName: key,
50
+ attribute,
51
+ });
38
52
 
39
53
  return processOrderBy(value, {
40
54
  db,
@@ -44,10 +58,6 @@ const processOrderBy = (orderBy, ctx) => {
44
58
  });
45
59
  }
46
60
 
47
- if (types.isScalar(attribute.type)) {
48
- return { column: `${alias}.${key}`, order: direction };
49
- }
50
-
51
61
  throw new Error(`You cannot order on ${attribute.type} types`);
52
62
  });
53
63
  }
@@ -33,7 +33,7 @@ const processPopulate = (populate, ctx) => {
33
33
 
34
34
  let populateMap = {};
35
35
 
36
- if (populate === false) {
36
+ if (populate === false || _.isNil(populate)) {
37
37
  return null;
38
38
  }
39
39
 
@@ -94,8 +94,17 @@ const processPopulate = (populate, ctx) => {
94
94
  return finalPopulate;
95
95
  };
96
96
 
97
- // Omit limit & offset to avoid needing a query per result to avoid making too many queries
98
- const pickPopulateParams = _.pick(['select', 'count', 'where', 'populate', 'orderBy']);
97
+ // TODO: Omit limit & offset to avoid needing a query per result to avoid making too many queries
98
+ const pickPopulateParams = _.pick([
99
+ 'select',
100
+ 'count',
101
+ 'where',
102
+ 'populate',
103
+ 'orderBy',
104
+ 'limit',
105
+ 'offset',
106
+ 'filters',
107
+ ]);
99
108
 
100
109
  // TODO: cleanup code
101
110
  // TODO: create aliases for pivot columns
@@ -114,7 +123,11 @@ const applyPopulate = async (results, populate, ctx) => {
114
123
  const attribute = meta.attributes[key];
115
124
  const targetMeta = db.metadata.get(attribute.target);
116
125
 
117
- const populateValue = pickPopulateParams(populate[key]);
126
+ const populateValue = {
127
+ filters: qb.state.filters,
128
+ ...pickPopulateParams(populate[key]),
129
+ };
130
+
118
131
  const isCount = populateValue.count === true;
119
132
 
120
133
  const fromTargetRow = rowOrRows => fromRow(targetMeta, rowOrRows);
@@ -181,7 +194,7 @@ const applyPopulate = async (results, populate, ctx) => {
181
194
  const rows = await qb
182
195
  .init(populateValue)
183
196
  .join({
184
- alias: alias,
197
+ alias,
185
198
  referencedTable: joinTable.name,
186
199
  referencedColumn: joinTable.inverseJoinColumn.name,
187
200
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -265,7 +278,7 @@ const applyPopulate = async (results, populate, ctx) => {
265
278
  const rows = await qb
266
279
  .init(populateValue)
267
280
  .join({
268
- alias: alias,
281
+ alias,
269
282
  referencedTable: joinTable.name,
270
283
  referencedColumn: joinTable.inverseJoinColumn.name,
271
284
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -299,7 +312,7 @@ const applyPopulate = async (results, populate, ctx) => {
299
312
  const rows = await qb
300
313
  .init(populateValue)
301
314
  .join({
302
- alias: alias,
315
+ alias,
303
316
  referencedTable: joinTable.name,
304
317
  referencedColumn: joinTable.inverseJoinColumn.name,
305
318
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -344,7 +357,7 @@ const applyPopulate = async (results, populate, ctx) => {
344
357
  const rows = await qb
345
358
  .init(populateValue)
346
359
  .join({
347
- alias: alias,
360
+ alias,
348
361
  referencedTable: joinTable.name,
349
362
  referencedColumn: joinTable.inverseJoinColumn.name,
350
363
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -378,7 +391,7 @@ const applyPopulate = async (results, populate, ctx) => {
378
391
  const rows = await qb
379
392
  .init(populateValue)
380
393
  .join({
381
- alias: alias,
394
+ alias,
382
395
  referencedTable: joinTable.name,
383
396
  referencedColumn: joinTable.inverseJoinColumn.name,
384
397
  rootColumn: joinTable.inverseJoinColumn.referencedColumn,
@@ -461,7 +474,7 @@ const applyPopulate = async (results, populate, ctx) => {
461
474
  const rows = await qb
462
475
  .init(populateValue)
463
476
  .join({
464
- alias: alias,
477
+ alias,
465
478
  referencedTable: joinTable.name,
466
479
  referencedColumn: joinColumn.name,
467
480
  rootColumn: joinColumn.referencedColumn,
@@ -538,6 +551,12 @@ const applyPopulate = async (results, populate, ctx) => {
538
551
  for (const type in idsByType) {
539
552
  const ids = idsByType[type];
540
553
 
554
+ // type was removed but still in morph relation
555
+ if (!db.metadata.get(type)) {
556
+ map[type] = {};
557
+ continue;
558
+ }
559
+
541
560
  const qb = db.entityManager.createQueryBuilder(type);
542
561
 
543
562
  const rows = await qb
@@ -596,6 +615,12 @@ const applyPopulate = async (results, populate, ctx) => {
596
615
  for (const type in idsByType) {
597
616
  const ids = idsByType[type];
598
617
 
618
+ // type was removed but still in morph relation
619
+ if (!db.metadata.get(type)) {
620
+ map[type] = {};
621
+ continue;
622
+ }
623
+
599
624
  const qb = db.entityManager.createQueryBuilder(type);
600
625
 
601
626
  const rows = await qb
@@ -3,11 +3,13 @@
3
3
  const _ = require('lodash/fp');
4
4
 
5
5
  const types = require('../../types');
6
+ const { toColumnName } = require('./transform');
6
7
 
7
- const applySearch = (qb, query, ctx) => {
8
- const { alias, uid, db } = ctx;
8
+ const applySearch = (knex, query, ctx) => {
9
+ const { qb, uid, db } = ctx;
10
+ const meta = db.metadata.get(uid);
9
11
 
10
- const { attributes } = db.metadata.get(uid);
12
+ const { attributes } = meta;
11
13
 
12
14
  const searchColumns = ['id'];
13
15
 
@@ -29,22 +31,34 @@ const applySearch = (qb, query, ctx) => {
29
31
 
30
32
  switch (db.dialect.client) {
31
33
  case 'postgres': {
32
- searchColumns.forEach(attr =>
33
- qb.orWhereRaw(`"${alias}"."${attr}"::text ILIKE ?`, `%${escapeQuery(query, '*%\\')}%`)
34
- );
34
+ searchColumns.forEach(attr => {
35
+ const columnName = toColumnName(meta, attr);
36
+ return knex.orWhereRaw(`??::text ILIKE ?`, [
37
+ qb.aliasColumn(columnName),
38
+ `%${escapeQuery(query, '*%\\')}%`,
39
+ ]);
40
+ });
35
41
 
36
42
  break;
37
43
  }
38
44
  case 'sqlite': {
39
- searchColumns.forEach(attr =>
40
- qb.orWhereRaw(`"${alias}"."${attr}" LIKE ? ESCAPE '\\'`, `%${escapeQuery(query, '*%\\')}%`)
41
- );
45
+ searchColumns.forEach(attr => {
46
+ const columnName = toColumnName(meta, attr);
47
+ return knex.orWhereRaw(`?? LIKE ? ESCAPE '\\'`, [
48
+ qb.aliasColumn(columnName),
49
+ `%${escapeQuery(query, '*%\\')}%`,
50
+ ]);
51
+ });
42
52
  break;
43
53
  }
44
54
  case 'mysql': {
45
- searchColumns.forEach(attr =>
46
- qb.orWhereRaw(`\`${alias}\`.\`${attr}\` LIKE ?`, `%${escapeQuery(query, '*%\\')}%`)
47
- );
55
+ searchColumns.forEach(attr => {
56
+ const columnName = toColumnName(meta, attr);
57
+ return knex.orWhereRaw(`?? LIKE ?`, [
58
+ qb.aliasColumn(columnName),
59
+ `%${escapeQuery(query, '*%\\')}%`,
60
+ ]);
61
+ });
48
62
  break;
49
63
  }
50
64
  default: {