@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,348 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash/fp');
4
+
5
+ const helpers = require('./helpers');
6
+
7
+ const createQueryBuilder = (uid, db) => {
8
+ const meta = db.metadata.get(uid);
9
+ const { tableName } = meta;
10
+
11
+ const state = {
12
+ type: 'select',
13
+ select: [],
14
+ count: null,
15
+ first: false,
16
+ data: null,
17
+ where: [],
18
+ joins: [],
19
+ populate: null,
20
+ limit: null,
21
+ offset: null,
22
+ orderBy: [],
23
+ groupBy: [],
24
+ };
25
+
26
+ let counter = 0;
27
+ const getAlias = () => `t${counter++}`;
28
+
29
+ return {
30
+ alias: getAlias(),
31
+ getAlias,
32
+
33
+ select(args) {
34
+ state.type = 'select';
35
+ state.select = _.uniq(_.castArray(args));
36
+
37
+ return this;
38
+ },
39
+
40
+ addSelect(args) {
41
+ state.select = _.uniq([...state.select, ..._.castArray(args)]);
42
+
43
+ return this;
44
+ },
45
+
46
+ insert(data) {
47
+ state.type = 'insert';
48
+ state.data = data;
49
+
50
+ return this;
51
+ },
52
+
53
+ delete() {
54
+ state.type = 'delete';
55
+
56
+ return this;
57
+ },
58
+
59
+ ref(name) {
60
+ return db.connection.ref(helpers.toColumnName(meta, name));
61
+ },
62
+
63
+ update(data) {
64
+ state.type = 'update';
65
+ state.data = data;
66
+
67
+ return this;
68
+ },
69
+
70
+ count(count = '*') {
71
+ state.type = 'count';
72
+ state.count = count;
73
+
74
+ return this;
75
+ },
76
+
77
+ where(where = {}) {
78
+ if (!_.isPlainObject(where)) {
79
+ throw new Error('Where must be an object');
80
+ }
81
+
82
+ state.where.push(where);
83
+
84
+ return this;
85
+ },
86
+
87
+ limit(limit) {
88
+ state.limit = limit;
89
+ return this;
90
+ },
91
+
92
+ offset(offset) {
93
+ state.offset = offset;
94
+ return this;
95
+ },
96
+
97
+ orderBy(orderBy) {
98
+ state.orderBy = orderBy;
99
+ return this;
100
+ },
101
+
102
+ groupBy(groupBy) {
103
+ state.groupBy = groupBy;
104
+ return this;
105
+ },
106
+
107
+ populate(populate) {
108
+ state.populate = populate;
109
+ return this;
110
+ },
111
+
112
+ search(query) {
113
+ state.search = query;
114
+ return this;
115
+ },
116
+
117
+ init(params = {}) {
118
+ const { _q, where, select, limit, offset, orderBy, groupBy, populate } = params;
119
+
120
+ if (!_.isNil(where)) {
121
+ this.where(where);
122
+ }
123
+
124
+ if (!_.isNil(_q)) {
125
+ this.search(_q);
126
+ }
127
+
128
+ if (!_.isNil(select)) {
129
+ this.select(select);
130
+ } else {
131
+ this.select('*');
132
+ }
133
+
134
+ if (!_.isNil(limit)) {
135
+ this.limit(limit);
136
+ }
137
+
138
+ if (!_.isNil(offset)) {
139
+ this.offset(offset);
140
+ }
141
+
142
+ if (!_.isNil(orderBy)) {
143
+ this.orderBy(orderBy);
144
+ }
145
+
146
+ if (!_.isNil(groupBy)) {
147
+ this.groupBy(groupBy);
148
+ }
149
+
150
+ if (!_.isNil(populate)) {
151
+ this.populate(populate);
152
+ }
153
+
154
+ return this;
155
+ },
156
+
157
+ first() {
158
+ state.first = true;
159
+ return this;
160
+ },
161
+
162
+ join(join) {
163
+ state.joins.push(join);
164
+ return this;
165
+ },
166
+
167
+ mustUseAlias() {
168
+ return ['select', 'count'].includes(state.type);
169
+ },
170
+
171
+ aliasColumn(key, alias) {
172
+ if (typeof key !== 'string') {
173
+ return key;
174
+ }
175
+
176
+ if (key.indexOf('.') >= 0) {
177
+ return key;
178
+ }
179
+
180
+ if (!_.isNil(alias)) {
181
+ return `${alias}.${key}`;
182
+ }
183
+
184
+ return this.mustUseAlias() ? `${this.alias}.${key}` : key;
185
+ },
186
+
187
+ raw(...args) {
188
+ return db.connection.raw(...args);
189
+ },
190
+
191
+ shouldUseSubQuery() {
192
+ return ['delete', 'update'].includes(state.type) && state.joins.length > 0;
193
+ },
194
+
195
+ runSubQuery() {
196
+ this.select('id');
197
+ const subQB = this.getKnexQuery();
198
+
199
+ const nestedSubQuery = db.connection.select('id').from(subQB.as('subQuery'));
200
+
201
+ return db
202
+ .connection(tableName)
203
+ [state.type]()
204
+ .whereIn('id', nestedSubQuery);
205
+ },
206
+
207
+ processState() {
208
+ state.orderBy = helpers.processOrderBy(state.orderBy, { qb: this, uid, db });
209
+ state.where = helpers.processWhere(state.where, { qb: this, uid, db });
210
+ state.populate = helpers.processPopulate(state.populate, { qb: this, uid, db });
211
+ state.data = helpers.toRow(meta, state.data);
212
+
213
+ this.processSelect();
214
+ },
215
+
216
+ shouldUseDistinct() {
217
+ return state.joins.length > 0 && _.isEmpty(state.groupBy);
218
+ },
219
+
220
+ processSelect() {
221
+ state.select = state.select.map(field => helpers.toColumnName(meta, field));
222
+
223
+ if (this.shouldUseDistinct()) {
224
+ const joinsOrderByColumns = state.joins.flatMap(join => {
225
+ return _.keys(join.orderBy).map(key => this.aliasColumn(key, join.alias));
226
+ });
227
+ const orderByColumns = state.orderBy.map(({ column }) => column);
228
+
229
+ state.select = _.uniq([...joinsOrderByColumns, ...orderByColumns, ...state.select]);
230
+ }
231
+ },
232
+
233
+ getKnexQuery() {
234
+ if (!state.type) {
235
+ this.select('*');
236
+ }
237
+
238
+ const aliasedTableName = this.mustUseAlias() ? { [this.alias]: tableName } : tableName;
239
+
240
+ const qb = db.connection(aliasedTableName);
241
+
242
+ if (this.shouldUseSubQuery()) {
243
+ return this.runSubQuery();
244
+ }
245
+
246
+ this.processState();
247
+
248
+ switch (state.type) {
249
+ case 'select': {
250
+ qb.select(state.select.map(column => this.aliasColumn(column)));
251
+
252
+ if (this.shouldUseDistinct()) {
253
+ qb.distinct();
254
+ }
255
+
256
+ break;
257
+ }
258
+ case 'count': {
259
+ qb.count({ count: state.count });
260
+ break;
261
+ }
262
+ case 'insert': {
263
+ qb.insert(state.data);
264
+
265
+ if (db.dialect.useReturning() && _.has('id', meta.attributes)) {
266
+ qb.returning('id');
267
+ }
268
+
269
+ break;
270
+ }
271
+ case 'update': {
272
+ qb.update(state.data);
273
+ break;
274
+ }
275
+ case 'delete': {
276
+ qb.delete();
277
+
278
+ break;
279
+ }
280
+ case 'truncate': {
281
+ db.truncate();
282
+ break;
283
+ }
284
+ }
285
+
286
+ if (state.limit) {
287
+ qb.limit(state.limit);
288
+ }
289
+
290
+ if (state.offset) {
291
+ qb.offset(state.offset);
292
+ }
293
+
294
+ if (state.orderBy.length > 0) {
295
+ qb.orderBy(state.orderBy);
296
+ }
297
+
298
+ if (state.first) {
299
+ qb.first();
300
+ }
301
+
302
+ if (state.groupBy.length > 0) {
303
+ qb.groupBy(state.groupBy);
304
+ }
305
+
306
+ // if there are joins and it is a delete or update use a sub query
307
+ if (state.where) {
308
+ helpers.applyWhere(qb, state.where);
309
+ }
310
+
311
+ // if there are joins and it is a delete or update use a sub query
312
+ if (state.search) {
313
+ qb.where(subQb => {
314
+ helpers.applySearch(subQb, state.search, { qb: this, db, uid });
315
+ });
316
+ }
317
+
318
+ if (state.joins.length > 0) {
319
+ helpers.applyJoins(qb, state.joins);
320
+ }
321
+
322
+ return qb;
323
+ },
324
+
325
+ async execute({ mapResults = true } = {}) {
326
+ try {
327
+ const qb = this.getKnexQuery();
328
+
329
+ const rows = await qb;
330
+
331
+ if (state.populate && !_.isNil(rows)) {
332
+ await helpers.applyPopulate(_.castArray(rows), state.populate, { qb: this, uid, db });
333
+ }
334
+
335
+ let results = rows;
336
+ if (mapResults && state.type === 'select') {
337
+ results = helpers.fromRow(meta, rows);
338
+ }
339
+
340
+ return results;
341
+ } catch (error) {
342
+ db.dialect.transformErrors(error);
343
+ }
344
+ },
345
+ };
346
+ };
347
+
348
+ module.exports = createQueryBuilder;
@@ -0,0 +1,181 @@
1
+ 'use strict';
2
+
3
+ const createSchemaDiff = require('../diff');
4
+
5
+ let diffSchemas;
6
+ describe('diffSchemas', () => {
7
+ beforeEach(() => {
8
+ const schemaDiff = createSchemaDiff({
9
+ dialect: {
10
+ usesForeignKeys() {
11
+ return true;
12
+ },
13
+ },
14
+ });
15
+
16
+ diffSchemas = schemaDiff.diff.bind(schemaDiff);
17
+ });
18
+
19
+ test('New Table', () => {
20
+ const testTable = {
21
+ name: 'my_table',
22
+ };
23
+
24
+ const srcSchema = {
25
+ tables: [],
26
+ };
27
+
28
+ const destSchema = {
29
+ tables: [testTable],
30
+ };
31
+
32
+ expect(diffSchemas(srcSchema, destSchema)).toStrictEqual({
33
+ status: 'CHANGED',
34
+ diff: {
35
+ tables: {
36
+ added: [testTable],
37
+ updated: [],
38
+ unchanged: [],
39
+ removed: [],
40
+ },
41
+ },
42
+ });
43
+ });
44
+
45
+ test('Removed Table', () => {
46
+ const testTable = {
47
+ name: 'my_table',
48
+ };
49
+
50
+ const srcSchema = {
51
+ tables: [testTable],
52
+ };
53
+
54
+ const destSchema = {
55
+ tables: [],
56
+ };
57
+
58
+ expect(diffSchemas(srcSchema, destSchema)).toStrictEqual({
59
+ status: 'CHANGED',
60
+ diff: {
61
+ tables: {
62
+ added: [],
63
+ updated: [],
64
+ unchanged: [],
65
+ removed: [testTable],
66
+ },
67
+ },
68
+ });
69
+ });
70
+
71
+ test('Unchanged Table', () => {
72
+ const testTable = {
73
+ name: 'my_table',
74
+ columns: [],
75
+ indexes: [],
76
+ foreignKeys: [],
77
+ };
78
+
79
+ const srcSchema = {
80
+ tables: [testTable],
81
+ };
82
+
83
+ const destSchema = {
84
+ tables: [testTable],
85
+ };
86
+
87
+ expect(diffSchemas(srcSchema, destSchema)).toStrictEqual({
88
+ status: 'UNCHANGED',
89
+ diff: {
90
+ tables: {
91
+ added: [],
92
+ updated: [],
93
+ unchanged: [testTable],
94
+ removed: [],
95
+ },
96
+ },
97
+ });
98
+ });
99
+
100
+ describe('Changed table', () => {
101
+ test('added column', () => {
102
+ const srcSchema = {
103
+ tables: [
104
+ {
105
+ name: 'my_table',
106
+ columns: [],
107
+ indexes: [],
108
+ foreignKeys: [],
109
+ },
110
+ ],
111
+ };
112
+
113
+ const destSchema = {
114
+ tables: [
115
+ {
116
+ name: 'my_table',
117
+ indexes: [],
118
+ foreignKeys: [],
119
+ columns: [
120
+ {
121
+ name: 'test_column',
122
+ },
123
+ ],
124
+ },
125
+ ],
126
+ };
127
+
128
+ expect(diffSchemas(srcSchema, destSchema)).toStrictEqual({
129
+ status: 'CHANGED',
130
+ diff: {
131
+ tables: {
132
+ added: [],
133
+ updated: [
134
+ {
135
+ name: 'my_table',
136
+ columns: {
137
+ added: [
138
+ {
139
+ name: 'test_column',
140
+ },
141
+ ],
142
+ updated: [],
143
+ unchanged: [],
144
+ removed: [],
145
+ },
146
+ foreignKeys: {
147
+ added: [],
148
+ updated: [],
149
+ unchanged: [],
150
+ removed: [],
151
+ },
152
+ indexes: {
153
+ added: [],
154
+ updated: [],
155
+ unchanged: [],
156
+ removed: [],
157
+ },
158
+ },
159
+ ],
160
+ unchanged: [],
161
+ removed: [],
162
+ },
163
+ },
164
+ });
165
+ });
166
+
167
+ test.todo('updated column');
168
+ test.todo('unchanged column');
169
+ test.todo('removed column');
170
+
171
+ test.todo('added index');
172
+ test.todo('updated index');
173
+ test.todo('unchanged index');
174
+ test.todo('removed index');
175
+
176
+ test.todo('added foreign key');
177
+ test.todo('updated foreign key');
178
+ test.todo('unchanged foreign key');
179
+ test.todo('removed foreign key');
180
+ });
181
+ });