@strapi/database 4.0.0-beta.3 → 4.0.0-beta.31

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.
@@ -123,10 +123,6 @@ class MysqlSchemaInspector {
123
123
  return schema;
124
124
  }
125
125
 
126
- getDatabaseSchema() {
127
- return this.db.connection.client.connectionSettings.schema || 'public';
128
- }
129
-
130
126
  async getTables() {
131
127
  const [rows] = await this.db.connection.raw(SQL_QUERIES.TABLE_LIST);
132
128
 
@@ -4,36 +4,39 @@ const SQL_QUERIES = {
4
4
  TABLE_LIST: /* sql */ `
5
5
  SELECT *
6
6
  FROM information_schema.tables
7
- WHERE table_schema = ? AND table_type = 'BASE TABLE';
7
+ WHERE
8
+ table_schema = ?
9
+ AND table_type = 'BASE TABLE'
10
+ AND table_name != 'geometry_columns'
11
+ AND table_name != 'spatial_ref_sys';
8
12
  `,
9
13
  LIST_COLUMNS: /* sql */ `
10
14
  SELECT data_type, column_name, character_maximum_length, column_default, is_nullable
11
15
  FROM information_schema.columns
12
- WHERE table_schema = ?
13
- AND table_name = ?;
16
+ WHERE table_schema = ? AND table_name = ?;
14
17
  `,
15
18
  INDEX_LIST: /* sql */ `
16
- select
19
+ SELECT
17
20
  ix.indexrelid,
18
21
  i.relname as index_name,
19
22
  a.attname as column_name,
20
23
  ix.indisunique as is_unique,
21
24
  ix.indisprimary as is_primary
22
- from
25
+ FROM
23
26
  pg_class t,
24
27
  pg_namespace s,
25
28
  pg_class i,
26
29
  pg_index ix,
27
30
  pg_attribute a
28
- where
31
+ WHERE
29
32
  t.oid = ix.indrelid
30
- and i.oid = ix.indexrelid
31
- and a.attrelid = t.oid
32
- and a.attnum = ANY(ix.indkey)
33
- and t.relkind = 'r'
34
- and t.relnamespace = s.oid
35
- and s.nspname = ?
36
- and t.relname = ?;
33
+ AND i.oid = ix.indexrelid
34
+ AND a.attrelid = t.oid
35
+ AND a.attnum = ANY(ix.indkey)
36
+ AND t.relkind = 'r'
37
+ AND t.relnamespace = s.oid
38
+ AND s.nspname = ?
39
+ AND t.relname = ?;
37
40
  `,
38
41
  FOREIGN_KEY_LIST: /* sql */ `
39
42
  SELECT
@@ -136,7 +139,7 @@ class PostgresqlSchemaInspector {
136
139
  }
137
140
 
138
141
  getDatabaseSchema() {
139
- return this.db.connection.client.connectionSettings.schema || 'public';
142
+ return this.db.connection.getSchemaName() || 'public';
140
143
  }
141
144
 
142
145
  async getTables() {
@@ -142,7 +142,7 @@ const createEntityManager = db => {
142
142
  await db.lifecycles.run('beforeCount', uid, { params });
143
143
 
144
144
  const res = await this.createQueryBuilder(uid)
145
- .init(_.pick(['_q', 'where'], params))
145
+ .init(_.pick(['_q', 'where', 'filters'], params))
146
146
  .count()
147
147
  .first()
148
148
  .execute();
@@ -172,7 +172,7 @@ const createEntityManager = db => {
172
172
 
173
173
  await this.attachRelations(uid, id, data);
174
174
 
175
- // TODO: in case there is not select or populate specified return the inserted data ?
175
+ // TODO: in case there is no select or populate specified return the inserted data ?
176
176
  // TODO: do not trigger the findOne lifecycles ?
177
177
  const result = await this.findOne(uid, {
178
178
  where: { id },
@@ -296,7 +296,7 @@ const createEntityManager = db => {
296
296
  throw new Error('Delete requires a where parameter');
297
297
  }
298
298
 
299
- // TODO: avoid trigger the findOne lifecycles in the case ?
299
+ // TODO: do not trigger the findOne lifecycles ?
300
300
  const entity = await this.findOne(uid, {
301
301
  select: select && ['id'].concat(select),
302
302
  where,
@@ -469,7 +469,6 @@ const createEntityManager = db => {
469
469
  const { joinTable } = attribute;
470
470
  const { joinColumn, inverseJoinColumn } = joinTable;
471
471
 
472
- // TODO: validate logic of delete
473
472
  if (isOneToAny(attribute) && isBidirectional(attribute)) {
474
473
  await this.createQueryBuilder(joinTable.name)
475
474
  .delete()
package/lib/errors.js CHANGED
@@ -1,14 +1,56 @@
1
1
  'use strict';
2
2
 
3
- class NotNullConstraint extends Error {
3
+ /* DatabaseError */
4
+ class DatabaseError extends Error {
5
+ constructor(message, details = {}) {
6
+ super();
7
+ this.name = 'DatabaseError';
8
+ this.message = message || 'A database error occured';
9
+ this.details = details;
10
+ }
11
+ }
12
+
13
+ class NotNullConstraint extends DatabaseError {
4
14
  constructor({ column = '' } = {}) {
5
15
  super();
6
16
  this.name = 'NotNullConstraint';
7
- this.message = `Not null constraint violation${column ? `on on column ${column}` : ''}.`;
17
+ this.message = `Not null constraint violation${column ? ` on column ${column}` : ''}.`;
18
+ this.details = { column };
8
19
  this.stack = '';
9
20
  }
10
21
  }
11
22
 
23
+ class InvalidTimeError extends DatabaseError {
24
+ constructor(message) {
25
+ super();
26
+ this.name = 'InvalidTimeFormat';
27
+ this.message = message || 'Invalid time format, expected HH:mm:ss.SSS';
28
+ this.details = {};
29
+ }
30
+ }
31
+
32
+ class InvalidDateError extends DatabaseError {
33
+ constructor(message) {
34
+ super();
35
+ this.name = 'InvalidTimeFormat';
36
+ this.message = message || 'Invalid date format, expected YYYY-MM-DD';
37
+ this.details = {};
38
+ }
39
+ }
40
+
41
+ class InvalidDateTimeError extends DatabaseError {
42
+ constructor(message) {
43
+ super();
44
+ this.name = 'InvalidTimeFormat';
45
+ this.message = message || 'Invalid datetime format, expected a timestamp or an ISO date';
46
+ this.details = {};
47
+ }
48
+ }
49
+
12
50
  module.exports = {
51
+ DatabaseError,
13
52
  NotNullConstraint,
53
+ InvalidTimeError,
54
+ InvalidDateError,
55
+ InvalidDateTimeError,
14
56
  };
package/lib/fields.js CHANGED
@@ -2,22 +2,13 @@
2
2
 
3
3
  const _ = require('lodash/fp');
4
4
  const dateFns = require('date-fns');
5
+ const { InvalidTimeError, InvalidDateError, InvalidDateTimeError } = require('./errors');
5
6
 
6
7
  class Field {
7
8
  constructor(config) {
8
9
  this.config = config;
9
10
  }
10
11
 
11
- // TODO: impl
12
- validate() {
13
- // // use config validators directly
14
- // if (this.config.validators) {
15
- // this.config.validators.forEach(validator => {
16
- // validator(value)
17
- // })
18
- // }
19
- }
20
-
21
12
  toDB(value) {
22
13
  return value;
23
14
  }
@@ -112,12 +103,12 @@ const parseTime = value => {
112
103
  if (dateFns.isDate(value)) return dateFns.format(value, 'HH:mm:ss.SSS');
113
104
 
114
105
  if (typeof value !== 'string') {
115
- throw new Error(`Expected a string, got a ${typeof value}`);
106
+ throw new InvalidTimeError(`Expected a string, got a ${typeof value}`);
116
107
  }
117
108
  const result = value.match(timeRegex);
118
109
 
119
110
  if (result === null) {
120
- throw new Error('Invalid time format, expected HH:mm:ss.SSS');
111
+ throw new InvalidTimeError('Invalid time format, expected HH:mm:ss.SSS');
121
112
  }
122
113
 
123
114
  const [, hours, minutes, seconds, fraction = '.000'] = result;
@@ -133,9 +124,9 @@ const parseDate = value => {
133
124
 
134
125
  if (dateFns.isValid(date)) return dateFns.format(date, 'yyyy-MM-dd');
135
126
 
136
- throw new Error(`Invalid format, expected an ISO compatible date`);
127
+ throw new InvalidDateError(`Invalid format, expected an ISO compatible date`);
137
128
  } catch (error) {
138
- throw new Error(`Invalid format, expected an ISO compatible date`);
129
+ throw new InvalidDateError(`Invalid format, expected an ISO compatible date`);
139
130
  }
140
131
  };
141
132
 
@@ -148,9 +139,9 @@ const parseDateTimeOrTimestamp = value => {
148
139
  const milliUnixDate = dateFns.parse(value, 'T', new Date());
149
140
  if (dateFns.isValid(milliUnixDate)) return milliUnixDate;
150
141
 
151
- throw new Error(`Invalid format, expected a timestamp or an ISO date`);
142
+ throw new InvalidDateTimeError(`Invalid format, expected a timestamp or an ISO date`);
152
143
  } catch (error) {
153
- throw new Error(`Invalid format, expected a timestamp or an ISO date`);
144
+ throw new InvalidDateTimeError(`Invalid format, expected a timestamp or an ISO date`);
154
145
  }
155
146
  };
156
147
 
package/lib/index.js CHANGED
@@ -8,20 +8,37 @@ const createMetadata = require('./metadata');
8
8
  const { createEntityManager } = require('./entity-manager');
9
9
  const { createMigrationsProvider } = require('./migrations');
10
10
  const { createLifecyclesProvider } = require('./lifecycles');
11
+ const errors = require('./errors');
11
12
 
12
13
  // TODO: move back into strapi
13
14
  const { transformContentTypes } = require('./utils/content-types');
14
15
 
16
+ const createConnection = config => {
17
+ const knexInstance = knex(config);
18
+
19
+ return Object.assign(knexInstance, {
20
+ getSchemaName() {
21
+ return this.client.connectionSettings.schema;
22
+ },
23
+ });
24
+ };
25
+
15
26
  class Database {
16
27
  constructor(config) {
17
28
  this.metadata = createMetadata(config.models);
18
29
 
19
- this.config = config;
30
+ this.config = {
31
+ connection: {},
32
+ settings: {
33
+ forceMigration: true,
34
+ },
35
+ ...config,
36
+ };
20
37
 
21
38
  this.dialect = getDialect(this);
22
39
  this.dialect.configure();
23
40
 
24
- this.connection = knex(this.config.connection);
41
+ this.connection = createConnection(this.config.connection);
25
42
 
26
43
  this.dialect.initialize();
27
44
 
@@ -41,6 +58,17 @@ class Database {
41
58
  return this.entityManager.getRepository(uid);
42
59
  }
43
60
 
61
+ getConnection(tableName) {
62
+ const schema = this.connection.getSchemaName();
63
+ const connection = tableName ? this.connection(tableName) : this.connection;
64
+ return schema ? connection.withSchema(schema) : connection;
65
+ }
66
+
67
+ getSchemaConnection(trx = this.connection) {
68
+ const schema = this.connection.getSchemaName();
69
+ return schema ? trx.schema.withSchema(schema) : trx.schema;
70
+ }
71
+
44
72
  queryBuilder(uid) {
45
73
  return this.entityManager.createQueryBuilder(uid);
46
74
  }
@@ -57,4 +85,5 @@ Database.init = async config => new Database(config);
57
85
 
58
86
  module.exports = {
59
87
  Database,
88
+ errors,
60
89
  };
@@ -27,7 +27,7 @@ const createUmzugProvider = db => {
27
27
 
28
28
  fse.ensureDirSync(migrationDir);
29
29
 
30
- const wrapFn = fn => db => db.connection.transaction(trx => Promise.resolve(fn(trx)));
30
+ const wrapFn = fn => db => db.getConnection().transaction(trx => Promise.resolve(fn(trx)));
31
31
  const storage = createStorage({ db, tableName: 'strapi_migrations' });
32
32
 
33
33
  return new Umzug({
@@ -1,13 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  const createStorage = (opts = {}) => {
4
- const tableName = opts.tableName || 'strapi_migrations';
5
- const knex = opts.db.connection;
4
+ const { db, tableName = 'strapi_migrations' } = opts;
6
5
 
7
- const hasMigrationTable = () => knex.schema.hasTable(tableName);
6
+ const hasMigrationTable = () => db.getSchemaConnection().hasTable(tableName);
8
7
 
9
8
  const createMigrationTable = () => {
10
- return knex.schema.createTable(tableName, table => {
9
+ return db.getSchemaConnection().createTable(tableName, table => {
11
10
  table.increments('id');
12
11
  table.string('name');
13
12
  table.datetime('time', { useTz: false });
@@ -16,7 +15,8 @@ const createStorage = (opts = {}) => {
16
15
 
17
16
  return {
18
17
  async logMigration(migrationName) {
19
- await knex
18
+ await db
19
+ .getConnection()
20
20
  .insert({
21
21
  name: migrationName,
22
22
  time: new Date(),
@@ -25,7 +25,8 @@ const createStorage = (opts = {}) => {
25
25
  },
26
26
 
27
27
  async unlogMigration(migrationName) {
28
- await knex(tableName)
28
+ await db
29
+ .getConnection(tableName)
29
30
  .del()
30
31
  .where({ name: migrationName });
31
32
  },
@@ -36,7 +37,8 @@ const createStorage = (opts = {}) => {
36
37
  return [];
37
38
  }
38
39
 
39
- const logs = await knex
40
+ const logs = await db
41
+ .getConnection(tableName)
40
42
  .select()
41
43
  .from(tableName)
42
44
  .orderBy('time');
@@ -67,7 +67,7 @@ const applyJoin = (qb, join) => {
67
67
  orderBy,
68
68
  } = join;
69
69
 
70
- qb[method]({ [alias]: referencedTable }, inner => {
70
+ qb[method](`${referencedTable} as ${alias}`, inner => {
71
71
  inner.on(`${rootTable}.${rootColumn}`, `${alias}.${referencedColumn}`);
72
72
 
73
73
  if (on) {
@@ -103,6 +103,7 @@ const pickPopulateParams = _.pick([
103
103
  'orderBy',
104
104
  'limit',
105
105
  'offset',
106
+ 'filters',
106
107
  ]);
107
108
 
108
109
  // TODO: cleanup code
@@ -122,7 +123,11 @@ const applyPopulate = async (results, populate, ctx) => {
122
123
  const attribute = meta.attributes[key];
123
124
  const targetMeta = db.metadata.get(attribute.target);
124
125
 
125
- const populateValue = pickPopulateParams(populate[key]);
126
+ const populateValue = {
127
+ filters: qb.state.filters,
128
+ ...pickPopulateParams(populate[key]),
129
+ };
130
+
126
131
  const isCount = populateValue.count === true;
127
132
 
128
133
  const fromTargetRow = rowOrRows => fromRow(targetMeta, rowOrRows);
@@ -3,6 +3,7 @@
3
3
  const _ = require('lodash/fp');
4
4
 
5
5
  const types = require('../../types');
6
+ const { createField } = require('../../fields');
6
7
  const { createJoin } = require('./join');
7
8
  const { toColumnName } = require('./transform');
8
9
 
@@ -28,10 +29,65 @@ const OPERATORS = [
28
29
  '$notContainsi',
29
30
  ];
30
31
 
32
+ const CAST_OPERATORS = [
33
+ '$not',
34
+ '$in',
35
+ '$notIn',
36
+ '$eq',
37
+ '$ne',
38
+ '$gt',
39
+ '$gte',
40
+ '$lt',
41
+ '$lte',
42
+ '$between',
43
+ ];
44
+
31
45
  const ARRAY_OPERATORS = ['$in', '$notIn', '$between'];
32
46
 
33
47
  const isOperator = key => OPERATORS.includes(key);
34
48
 
49
+ const castValue = (value, attribute) => {
50
+ if (!attribute) {
51
+ return value;
52
+ }
53
+
54
+ if (types.isScalar(attribute.type)) {
55
+ const field = createField(attribute);
56
+
57
+ return value === null ? null : field.toDB(value);
58
+ }
59
+
60
+ return value;
61
+ };
62
+
63
+ const processAttributeWhere = (attribute, where, operator = '$eq') => {
64
+ if (_.isArray(where)) {
65
+ return where.map(sub => processAttributeWhere(attribute, sub, operator));
66
+ }
67
+
68
+ if (!_.isPlainObject(where)) {
69
+ if (CAST_OPERATORS.includes(operator)) {
70
+ return castValue(where, attribute);
71
+ }
72
+
73
+ return where;
74
+ }
75
+
76
+ const filters = {};
77
+
78
+ for (const key in where) {
79
+ const value = where[key];
80
+
81
+ if (!isOperator(key)) {
82
+ throw new Error(`Undefined attribute level operator ${key}`);
83
+ }
84
+
85
+ filters[key] = processAttributeWhere(attribute, value, key);
86
+ }
87
+
88
+ return filters;
89
+ };
90
+
35
91
  /**
36
92
  * Process where parameter
37
93
  * @param {Object} where
@@ -39,7 +95,7 @@ const isOperator = key => OPERATORS.includes(key);
39
95
  * @param {number} depth
40
96
  * @returns {Object}
41
97
  */
42
- const processWhere = (where, ctx, depth = 0) => {
98
+ const processWhere = (where, ctx) => {
43
99
  if (!_.isArray(where) && !_.isPlainObject(where)) {
44
100
  throw new Error('Where must be an array or an object');
45
101
  }
@@ -53,7 +109,7 @@ const processWhere = (where, ctx, depth = 0) => {
53
109
  return where;
54
110
  }
55
111
 
56
- return processWhere(where, ctx, depth + 1);
112
+ return processWhere(where, ctx);
57
113
  };
58
114
 
59
115
  const { db, uid, qb, alias } = ctx;
@@ -64,7 +120,6 @@ const processWhere = (where, ctx, depth = 0) => {
64
120
  // for each key in where
65
121
  for (const key in where) {
66
122
  const value = where[key];
67
- const attribute = meta.attributes[key];
68
123
 
69
124
  // if operator $and $or then loop over them
70
125
  if (GROUP_OPERATORS.includes(key)) {
@@ -78,28 +133,17 @@ const processWhere = (where, ctx, depth = 0) => {
78
133
  }
79
134
 
80
135
  if (isOperator(key)) {
81
- if (depth == 0) {
82
- throw new Error(
83
- `Only $and, $or and $not can by used as root level operators. Found ${key}.`
84
- );
85
- }
86
-
87
- filters[key] = processNested(value, ctx);
88
- continue;
136
+ throw new Error(`Only $and, $or and $not can by used as root level operators. Found ${key}.`);
89
137
  }
90
138
 
91
- if (!attribute) {
92
- filters[qb.aliasColumn(key, alias)] = processNested(value, ctx);
139
+ const attribute = meta.attributes[key];
93
140
 
141
+ if (!attribute) {
142
+ filters[qb.aliasColumn(key, alias)] = processAttributeWhere(null, value);
94
143
  continue;
95
-
96
- // throw new Error(`Attribute ${key} not found on model ${uid}`);
97
144
  }
98
145
 
99
- // move to if else to check for scalar / relation / components & throw for other types
100
- if (attribute.type === 'relation') {
101
- // TODO: pass down some filters (e.g published at)
102
-
146
+ if (types.isRelation(attribute.type)) {
103
147
  // attribute
104
148
  const subAlias = createJoin(ctx, {
105
149
  alias: alias || qb.alias,
@@ -127,9 +171,10 @@ const processWhere = (where, ctx, depth = 0) => {
127
171
 
128
172
  if (types.isScalar(attribute.type)) {
129
173
  const columnName = toColumnName(meta, key);
174
+ const aliasedColumnName = qb.aliasColumn(columnName, alias);
175
+
176
+ filters[aliasedColumnName] = processAttributeWhere(attribute, value);
130
177
 
131
- // TODO: cast to DB type
132
- filters[qb.aliasColumn(columnName, alias)] = processNested(value, ctx);
133
178
  continue;
134
179
  }
135
180
 
@@ -139,6 +184,7 @@ const processWhere = (where, ctx, depth = 0) => {
139
184
  return filters;
140
185
  };
141
186
 
187
+ // TODO: add type casting per operator at some point
142
188
  const applyOperator = (qb, column, operator, value) => {
143
189
  if (Array.isArray(value) && !ARRAY_OPERATORS.includes(operator)) {
144
190
  return qb.where(subQB => {
@@ -201,7 +247,6 @@ const applyOperator = (qb, column, operator, value) => {
201
247
  break;
202
248
  }
203
249
  case '$null': {
204
- // TODO: make this better
205
250
  if (value) {
206
251
  qb.whereNull(column);
207
252
  }
@@ -211,15 +256,12 @@ const applyOperator = (qb, column, operator, value) => {
211
256
  if (value) {
212
257
  qb.whereNotNull(column);
213
258
  }
214
-
215
259
  break;
216
260
  }
217
261
  case '$between': {
218
262
  qb.whereBetween(column, value);
219
263
  break;
220
264
  }
221
-
222
- // TODO: add casting logic
223
265
  case '$startsWith': {
224
266
  qb.where(column, 'like', `${value}%`);
225
267
  break;
@@ -253,7 +295,7 @@ const applyOperator = (qb, column, operator, value) => {
253
295
  // TODO: relational operators every/some/exists/size ...
254
296
 
255
297
  default: {
256
- throw new Error(`Undefined operator ${operator}`);
298
+ throw new Error(`Undefined attribute level operator ${operator}`);
257
299
  }
258
300
  }
259
301
  };
@@ -267,7 +309,6 @@ const applyWhereToColumn = (qb, column, columnWhere) => {
267
309
  return qb.where(column, columnWhere);
268
310
  }
269
311
 
270
- // TODO: handle casing
271
312
  Object.keys(columnWhere).forEach(operator => {
272
313
  const value = columnWhere[operator];
273
314
 
@@ -29,6 +29,7 @@ const createQueryBuilder = (uid, db) => {
29
29
  return {
30
30
  alias: getAlias(),
31
31
  getAlias,
32
+ state,
32
33
 
33
34
  select(args) {
34
35
  state.type = 'select';
@@ -115,7 +116,7 @@ const createQueryBuilder = (uid, db) => {
115
116
  },
116
117
 
117
118
  init(params = {}) {
118
- const { _q, where, select, limit, offset, orderBy, groupBy, populate } = params;
119
+ const { _q, filters, where, select, limit, offset, orderBy, groupBy, populate } = params;
119
120
 
120
121
  if (!_.isNil(where)) {
121
122
  this.where(where);
@@ -151,9 +152,17 @@ const createQueryBuilder = (uid, db) => {
151
152
  this.populate(populate);
152
153
  }
153
154
 
155
+ if (!_.isNil(filters)) {
156
+ this.filters(filters);
157
+ }
158
+
154
159
  return this;
155
160
  },
156
161
 
162
+ filters(filters) {
163
+ state.filters = filters;
164
+ },
165
+
157
166
  first() {
158
167
  state.first = true;
159
168
  return this;
@@ -196,16 +205,32 @@ const createQueryBuilder = (uid, db) => {
196
205
  this.select('id');
197
206
  const subQB = this.getKnexQuery();
198
207
 
199
- const nestedSubQuery = db.connection.select('id').from(subQB.as('subQuery'));
208
+ const nestedSubQuery = db
209
+ .getConnection()
210
+ .select('id')
211
+ .from(subQB.as('subQuery'));
200
212
 
201
213
  return db
202
- .connection(tableName)
214
+ .getConnection(tableName)
203
215
  [state.type]()
204
216
  .whereIn('id', nestedSubQuery);
205
217
  },
206
218
 
207
219
  processState() {
208
220
  state.orderBy = helpers.processOrderBy(state.orderBy, { qb: this, uid, db });
221
+
222
+ if (!_.isNil(state.filters)) {
223
+ if (_.isFunction(state.filters)) {
224
+ const filters = state.filters({ qb: this, uid, meta, db });
225
+
226
+ if (!_.isNil(filters)) {
227
+ state.where.push(filters);
228
+ }
229
+ } else {
230
+ state.where.push(state.filters);
231
+ }
232
+ }
233
+
209
234
  state.where = helpers.processWhere(state.where, { qb: this, uid, db });
210
235
  state.populate = helpers.processPopulate(state.populate, { qb: this, uid, db });
211
236
  state.data = helpers.toRow(meta, state.data);
@@ -235,9 +260,9 @@ const createQueryBuilder = (uid, db) => {
235
260
  this.select('*');
236
261
  }
237
262
 
238
- const aliasedTableName = this.mustUseAlias() ? { [this.alias]: tableName } : tableName;
263
+ const aliasedTableName = this.mustUseAlias() ? `${tableName} as ${this.alias}` : tableName;
239
264
 
240
- const qb = db.connection(aliasedTableName);
265
+ const qb = db.getConnection(aliasedTableName);
241
266
 
242
267
  if (this.shouldUseSubQuery()) {
243
268
  return this.runSubQuery();
@@ -11,8 +11,8 @@ module.exports = db => {
11
11
  * Returns a knex schema builder instance
12
12
  * @param {string} table - table name
13
13
  */
14
- getSchemaBuilder(table, trx = db.connection) {
15
- return table.schema ? trx.schema.withSchema(table.schema) : trx.schema;
14
+ getSchemaBuilder(trx) {
15
+ return db.getSchemaConnection(trx);
16
16
  },
17
17
 
18
18
  /**
@@ -20,10 +20,7 @@ module.exports = db => {
20
20
  * @param {Schema} schema - database schema
21
21
  */
22
22
  async createSchema(schema) {
23
- // TODO: ensure database exists;
24
-
25
23
  await db.connection.transaction(async trx => {
26
- // create tables without FKs first do avoid ordering issues
27
24
  await this.createTables(schema.tables, trx);
28
25
  });
29
26
  },
@@ -36,14 +33,14 @@ module.exports = db => {
36
33
  async createTables(tables, trx) {
37
34
  for (const table of tables) {
38
35
  debug(`Creating table: ${table.name}`);
39
- const schemaBuilder = this.getSchemaBuilder(table, trx);
36
+ const schemaBuilder = this.getSchemaBuilder(trx);
40
37
  await helpers.createTable(schemaBuilder, table);
41
38
  }
42
39
 
43
40
  // create FKs once all the tables exist
44
41
  for (const table of tables) {
45
42
  debug(`Creating table foreign keys: ${table.name}`);
46
- const schemaBuilder = this.getSchemaBuilder(table, trx);
43
+ const schemaBuilder = this.getSchemaBuilder(trx);
47
44
  await helpers.createTableForeignKeys(schemaBuilder, table);
48
45
  }
49
46
  },
@@ -61,7 +58,7 @@ module.exports = db => {
61
58
 
62
59
  await db.connection.transaction(async trx => {
63
60
  for (const table of schema.tables.reverse()) {
64
- const schemaBuilder = this.getSchemaBuilder(table, trx);
61
+ const schemaBuilder = this.getSchemaBuilder(trx);
65
62
  await helpers.dropTable(schemaBuilder, table);
66
63
  }
67
64
  });
@@ -73,29 +70,33 @@ module.exports = db => {
73
70
  */
74
71
  // TODO: implement force option to disable removal in DB
75
72
  async updateSchema(schemaDiff) {
73
+ const { forceMigration } = db.config.settings;
74
+
76
75
  await db.dialect.startSchemaUpdate();
77
76
  await db.connection.transaction(async trx => {
78
77
  await this.createTables(schemaDiff.tables.added, trx);
79
78
 
80
- // drop all delete table foreign keys then delete the tables
81
- for (const table of schemaDiff.tables.removed) {
82
- debug(`Removing table foreign keys: ${table.name}`);
79
+ if (forceMigration) {
80
+ // drop all delete table foreign keys then delete the tables
81
+ for (const table of schemaDiff.tables.removed) {
82
+ debug(`Removing table foreign keys: ${table.name}`);
83
83
 
84
- const schemaBuilder = this.getSchemaBuilder(table, trx);
85
- await helpers.dropTableForeignKeys(schemaBuilder, table);
86
- }
84
+ const schemaBuilder = this.getSchemaBuilder(trx);
85
+ await helpers.dropTableForeignKeys(schemaBuilder, table);
86
+ }
87
87
 
88
- for (const table of schemaDiff.tables.removed) {
89
- debug(`Removing table: ${table.name}`);
88
+ for (const table of schemaDiff.tables.removed) {
89
+ debug(`Removing table: ${table.name}`);
90
90
 
91
- const schemaBuilder = this.getSchemaBuilder(table, trx);
92
- await helpers.dropTable(schemaBuilder, table);
91
+ const schemaBuilder = this.getSchemaBuilder(trx);
92
+ await helpers.dropTable(schemaBuilder, table);
93
+ }
93
94
  }
94
95
 
95
96
  for (const table of schemaDiff.tables.updated) {
96
97
  debug(`Updating table: ${table.name}`);
97
98
  // alter table
98
- const schemaBuilder = this.getSchemaBuilder(table, trx);
99
+ const schemaBuilder = this.getSchemaBuilder(trx);
99
100
 
100
101
  await helpers.alterTable(schemaBuilder, table);
101
102
  }
@@ -118,7 +119,11 @@ const createHelpers = db => {
118
119
  const constraint = tableBuilder
119
120
  .foreign(columns, name)
120
121
  .references(referencedColumns)
121
- .inTable(referencedTable);
122
+ .inTable(
123
+ db.connection.getSchemaName()
124
+ ? `${db.connection.getSchemaName()}.${referencedTable}`
125
+ : referencedTable
126
+ );
122
127
 
123
128
  if (onDelete) {
124
129
  constraint.onDelete(onDelete);
@@ -167,6 +172,10 @@ const createHelpers = db => {
167
172
  * @param {Index} index
168
173
  */
169
174
  const dropIndex = (tableBuilder, index) => {
175
+ if (!db.config.settings.forceMigration) {
176
+ return;
177
+ }
178
+
170
179
  const { type, columns, name } = index;
171
180
 
172
181
  switch (type) {
@@ -221,7 +230,11 @@ const createHelpers = db => {
221
230
  * @param {Column} column
222
231
  */
223
232
  const dropColumn = (tableBuilder, column) => {
224
- tableBuilder.dropColumn(column.name);
233
+ if (!db.config.settings.forceMigration) {
234
+ return;
235
+ }
236
+
237
+ return tableBuilder.dropColumn(column.name);
225
238
  };
226
239
 
227
240
  /**
@@ -316,7 +329,13 @@ const createHelpers = db => {
316
329
  * @param {Knex.SchemaBuilder} schemaBuilder
317
330
  * @param {Table} table
318
331
  */
319
- const dropTable = (schemaBuilder, table) => schemaBuilder.dropTableIfExists(table.name);
332
+ const dropTable = (schemaBuilder, table) => {
333
+ if (!db.config.settings.forceMigration) {
334
+ return;
335
+ }
336
+
337
+ return schemaBuilder.dropTableIfExists(table.name);
338
+ };
320
339
 
321
340
  /**
322
341
  * Creates a table foreign keys constraints
@@ -336,6 +355,10 @@ const createHelpers = db => {
336
355
  * @param {Table} table
337
356
  */
338
357
  const dropTableForeignKeys = async (schemaBuilder, table) => {
358
+ if (!db.config.settings.forceMigration) {
359
+ return;
360
+ }
361
+
339
362
  // foreign keys
340
363
  await schemaBuilder.table(table.name, tableBuilder => {
341
364
  (table.foreignKeys || []).forEach(foreignKey => dropForeignKey(tableBuilder, foreignKey));
@@ -5,10 +5,10 @@ const crypto = require('crypto');
5
5
  const TABLE_NAME = 'strapi_database_schema';
6
6
 
7
7
  module.exports = db => {
8
- const hasSchemaTable = () => db.connection.schema.hasTable(TABLE_NAME);
8
+ const hasSchemaTable = () => db.getSchemaConnection().hasTable(TABLE_NAME);
9
9
 
10
10
  const createSchemaTable = () => {
11
- return db.connection.schema.createTable(TABLE_NAME, t => {
11
+ return db.getSchemaConnection().createTable(TABLE_NAME, t => {
12
12
  t.increments('id');
13
13
  t.json('schema');
14
14
  t.datetime('time', { useTz: false });
@@ -26,7 +26,8 @@ module.exports = db => {
26
26
  async read() {
27
27
  await checkTableExists();
28
28
 
29
- const res = await db.connection
29
+ const res = await db
30
+ .getConnection()
30
31
  .select('*')
31
32
  .from(TABLE_NAME)
32
33
  .orderBy('time', 'DESC')
@@ -55,21 +56,24 @@ module.exports = db => {
55
56
  await checkTableExists();
56
57
 
57
58
  // NOTE: we can remove this to add history
58
- await db.connection(TABLE_NAME).delete();
59
+ await db.getConnection(TABLE_NAME).delete();
59
60
 
60
61
  const time = new Date();
61
62
 
62
- await db.connection(TABLE_NAME).insert({
63
- schema: JSON.stringify(schema),
64
- hash: this.hashSchema(schema),
65
- time,
66
- });
63
+ await db
64
+ .getConnection()
65
+ .insert({
66
+ schema: JSON.stringify(schema),
67
+ hash: this.hashSchema(schema),
68
+ time,
69
+ })
70
+ .into(TABLE_NAME);
67
71
  },
68
72
 
69
73
  async clear() {
70
74
  await checkTableExists();
71
75
 
72
- await db.connection(TABLE_NAME).truncate();
76
+ await db.getConnection(TABLE_NAME).truncate();
73
77
  },
74
78
  };
75
79
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/database",
3
- "version": "4.0.0-beta.3",
3
+ "version": "4.0.0-beta.31",
4
4
  "description": "Strapi's database layer",
5
5
  "homepage": "https://strapi.io",
6
6
  "main": "./lib/index.js",
@@ -35,5 +35,5 @@
35
35
  "lodash": "4.17.21",
36
36
  "umzug": "2.3.0"
37
37
  },
38
- "gitHead": "5501518e6c4290f6b285f7fd065953381a61b08a"
38
+ "gitHead": "965d27fa02fd6908db602a25069795f304167f2c"
39
39
  }
Binary file