@nocobase/database 0.7.0-alpha.82 → 0.7.1-alpha.4

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 (54) hide show
  1. package/lib/collection-importer.js +25 -42
  2. package/lib/collection.d.ts +6 -2
  3. package/lib/collection.js +37 -5
  4. package/lib/database.d.ts +21 -5
  5. package/lib/database.js +161 -49
  6. package/lib/fields/field.d.ts +4 -1
  7. package/lib/fields/field.js +117 -0
  8. package/lib/fields/formula-field.d.ts +19 -0
  9. package/lib/fields/formula-field.js +184 -0
  10. package/lib/fields/index.d.ts +3 -1
  11. package/lib/fields/index.js +13 -0
  12. package/lib/index.d.ts +1 -0
  13. package/lib/index.js +14 -0
  14. package/lib/migration.d.ts +35 -0
  15. package/lib/migration.js +90 -0
  16. package/lib/mock-database.d.ts +1 -0
  17. package/lib/mock-database.js +2 -1
  18. package/lib/model-hook.d.ts +5 -5
  19. package/lib/model-hook.js +26 -18
  20. package/lib/options-parser.js +65 -43
  21. package/lib/relation-repository/relation-repository.js +11 -1
  22. package/lib/relation-repository/single-relation-repository.js +8 -1
  23. package/lib/repository.js +12 -4
  24. package/lib/update-associations.js +1 -1
  25. package/package.json +9 -4
  26. package/src/__tests__/collection.test.ts +27 -0
  27. package/src/__tests__/database.test.ts +47 -0
  28. package/src/__tests__/fields/formula-field.test.ts +69 -0
  29. package/src/__tests__/fixtures/migrations/m1.ts +7 -0
  30. package/src/__tests__/fixtures/migrations/m2.ts +7 -0
  31. package/src/__tests__/hooks/afterCreateWithAssociations.test.ts +33 -0
  32. package/src/__tests__/migrator.test.ts +70 -0
  33. package/src/__tests__/model-hook.test.ts +54 -0
  34. package/src/__tests__/option-parser.test.ts +10 -6
  35. package/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +1 -1
  36. package/src/__tests__/sequelize-hooks.test.ts +69 -0
  37. package/src/__tests__/sort.test.ts +51 -0
  38. package/src/__tests__/update-associations.test.ts +3 -3
  39. package/src/collection-importer.ts +12 -20
  40. package/src/collection.ts +26 -2
  41. package/src/database.ts +112 -29
  42. package/src/fields/field.ts +88 -1
  43. package/src/fields/formula-field.ts +106 -0
  44. package/src/fields/index.ts +3 -0
  45. package/src/index.ts +1 -0
  46. package/src/migration.ts +76 -0
  47. package/src/mock-database.ts +1 -0
  48. package/src/model-hook.ts +25 -21
  49. package/src/options-parser.ts +13 -9
  50. package/src/relation-repository/multiple-relation-repository.ts +8 -2
  51. package/src/relation-repository/relation-repository.ts +1 -0
  52. package/src/relation-repository/single-relation-repository.ts +5 -1
  53. package/src/repository.ts +16 -4
  54. package/src/update-associations.ts +1 -1
@@ -1,7 +1,7 @@
1
1
  import { Collection } from '../collection';
2
- import { mockDatabase } from './index';
3
- import { OptionsParser } from '../options-parser';
4
2
  import { Database } from '../database';
3
+ import { OptionsParser } from '../options-parser';
4
+ import { mockDatabase } from './index';
5
5
 
6
6
  describe('option parser', () => {
7
7
  let db: Database;
@@ -83,6 +83,10 @@ describe('option parser', () => {
83
83
  });
84
84
 
85
85
  test('with sort option', () => {
86
+ if (db.inDialect('mysql')) {
87
+ expect(1).toBe(1);
88
+ return;
89
+ }
86
90
  let options: any = {
87
91
  sort: ['id'],
88
92
  };
@@ -91,7 +95,7 @@ describe('option parser', () => {
91
95
  collection: User,
92
96
  });
93
97
  let params = parser.toSequelizeParams();
94
- expect(params['order']).toEqual([['id', 'ASC']]);
98
+ expect(params['order']).toEqual([['id', 'ASC NULLS LAST']]);
95
99
 
96
100
  options = {
97
101
  sort: ['id', '-posts.title', 'posts.comments.createdAt'],
@@ -102,9 +106,9 @@ describe('option parser', () => {
102
106
  });
103
107
  params = parser.toSequelizeParams();
104
108
  expect(params['order']).toEqual([
105
- ['id', 'ASC'],
106
- [Post.model, 'title', 'DESC'],
107
- [Post.model, Comment.model, 'createdAt', 'ASC'],
109
+ ['id', 'ASC NULLS LAST'],
110
+ [Post.model, 'title', 'DESC NULLS LAST'],
111
+ [Post.model, Comment.model, 'createdAt', 'ASC NULLS LAST'],
108
112
  ]);
109
113
  });
110
114
 
@@ -308,7 +308,7 @@ describe('belongs to many', () => {
308
308
  offset: 0,
309
309
  });
310
310
 
311
- console.log(tags);
311
+ // console.log(tags);
312
312
  });
313
313
 
314
314
  test('update raw attribute', async () => {
@@ -0,0 +1,69 @@
1
+ import { Database } from '../database';
2
+ import { mockDatabase } from './index';
3
+
4
+ // TODO
5
+ describe('sequelize-hooks', () => {
6
+ let db: Database;
7
+
8
+ beforeEach(async () => {
9
+ db = mockDatabase();
10
+ await db.sync();
11
+ });
12
+
13
+ afterEach(async () => {
14
+ await db.close();
15
+ });
16
+
17
+ test('exec order', async () => {
18
+ const collection = db.collection({
19
+ name: 't_test',
20
+ });
21
+ const orders = [];
22
+ db.on('beforeCreate', () => {
23
+ orders.push('beforeCreate');
24
+ });
25
+ db.on('t_test.beforeCreate', () => {
26
+ orders.push('model.beforeCreate');
27
+ });
28
+ db.on('afterCreate', () => {
29
+ orders.push('afterCreate');
30
+ });
31
+ db.on('t_test.afterCreate', () => {
32
+ orders.push('model.afterCreate');
33
+ });
34
+ await collection.sync();
35
+ await collection.model.create();
36
+ expect(orders).toEqual([
37
+ 'model.beforeCreate',
38
+ 'beforeCreate',
39
+ 'model.afterCreate',
40
+ 'afterCreate'
41
+ ]);
42
+ });
43
+
44
+ describe('afterSync', () => {
45
+ test('singular name', async () => {
46
+ const collection = db.collection({
47
+ name: 't_test',
48
+ });
49
+ const spy = jest.fn();
50
+ db.on('t_test.afterSync', () => {
51
+ spy('afterSync');
52
+ });
53
+ await collection.sync();
54
+ expect(spy).toHaveBeenCalledTimes(1);
55
+ });
56
+
57
+ test('plural name', async () => {
58
+ const collection = db.collection({
59
+ name: 't_tests',
60
+ });
61
+ const spy = jest.fn();
62
+ db.on('t_tests.afterSync', () => {
63
+ spy('afterSync');
64
+ });
65
+ await collection.sync();
66
+ expect(spy).toHaveBeenCalledTimes(1);
67
+ });
68
+ });
69
+ });
@@ -0,0 +1,51 @@
1
+ import { Database } from '../database';
2
+ import { mockDatabase } from './';
3
+
4
+ describe('sort', function () {
5
+ let db: Database;
6
+
7
+ beforeEach(async () => {
8
+ db = mockDatabase();
9
+ });
10
+
11
+ afterEach(async () => {
12
+ await db.close();
13
+ });
14
+
15
+ test('order nulls last', async () => {
16
+ const Test = db.collection({
17
+ name: 'test',
18
+ fields: [
19
+ {
20
+ type: 'integer',
21
+ name: 'num',
22
+ },
23
+ ],
24
+ });
25
+ await Test.sync();
26
+ await Test.model.bulkCreate([
27
+ {
28
+ num: 3,
29
+ },
30
+ {
31
+ num: 2,
32
+ },
33
+ {
34
+ num: null,
35
+ },
36
+ {
37
+ num: 1,
38
+ },
39
+ ]);
40
+ const items = await Test.repository.find({
41
+ sort: '-num',
42
+ });
43
+ const nums = items.map((item) => item.get('num'));
44
+ expect(nums).toEqual([3,2,1,null]);
45
+ const items2 = await Test.repository.find({
46
+ sort: 'num',
47
+ });
48
+ const nums2 = items2.map((item) => item.get('num'));
49
+ expect(nums2).toEqual([1,2,3,null]);
50
+ });
51
+ });
@@ -173,7 +173,7 @@ describe('update associations', () => {
173
173
  await updateAssociations(user1, {
174
174
  posts: post1,
175
175
  });
176
- console.log(JSON.stringify(user1, null, 2));
176
+
177
177
  expect(user1.toJSON()).toMatchObject({
178
178
  name: 'user1',
179
179
  });
@@ -191,7 +191,7 @@ describe('update associations', () => {
191
191
  name: 'post111',
192
192
  },
193
193
  });
194
- console.log(JSON.stringify(user1, null, 2));
194
+
195
195
  expect(user1.toJSON()).toMatchObject({
196
196
  name: 'user1',
197
197
  });
@@ -216,7 +216,7 @@ describe('update associations', () => {
216
216
  post3,
217
217
  ],
218
218
  });
219
- console.log(JSON.stringify(user1, null, 2));
219
+
220
220
  expect(user1.toJSON()).toMatchObject({
221
221
  name: 'user1',
222
222
  });
@@ -1,20 +1,10 @@
1
- import * as fs from 'fs';
2
- import lodash from 'lodash';
3
1
  import path from 'path';
2
+ import { readdir } from 'fs/promises';
3
+ import { isPlainObject } from 'lodash';
4
+ import { requireModule } from '@nocobase/utils';
4
5
 
5
6
  export type ImportFileExtension = 'js' | 'ts' | 'json';
6
7
 
7
- async function requireModule(module: any) {
8
- if (typeof module === 'string') {
9
- module = require(module);
10
- }
11
-
12
- if (typeof module !== 'object') {
13
- return module;
14
- }
15
- return module.__esModule ? module.default : module;
16
- }
17
-
18
8
  export class ImporterReader {
19
9
  directory: string;
20
10
  extensions: Set<string>;
@@ -30,11 +20,10 @@ export class ImporterReader {
30
20
  }
31
21
 
32
22
  async read() {
33
- const modules = (
34
- await fs.promises.readdir(this.directory, {
35
- encoding: 'utf-8',
36
- })
37
- )
23
+ const files = await readdir(this.directory, {
24
+ encoding: 'utf-8',
25
+ });
26
+ const modules = files
38
27
  .filter((fileName) => {
39
28
  if (fileName.endsWith('.d.ts')) {
40
29
  return false;
@@ -42,8 +31,11 @@ export class ImporterReader {
42
31
  const ext = path.parse(fileName).ext.replace('.', '');
43
32
  return this.extensions.has(ext);
44
33
  })
45
- .map(async (fileName) => await requireModule(path.join(this.directory, fileName)));
34
+ .map((fileName) => {
35
+ const mod = requireModule(path.join(this.directory, fileName));
36
+ return typeof mod === 'function' ? mod() : mod;
37
+ });
46
38
 
47
- return (await Promise.all(modules)).filter((module) => lodash.isPlainObject(module));
39
+ return (await Promise.all(modules)).filter((module) => isPlainObject(module));
48
40
  }
49
41
  }
package/src/collection.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import merge from 'deepmerge';
2
2
  import { EventEmitter } from 'events';
3
3
  import { default as lodash, default as _ } from 'lodash';
4
- import { col, ModelCtor, ModelOptions, SyncOptions } from 'sequelize';
4
+ import { ModelCtor, ModelOptions, QueryInterfaceDropTableOptions, SyncOptions, Transactionable } from 'sequelize';
5
5
  import { Database } from './database';
6
6
  import { Field, FieldOptions } from './fields';
7
7
  import { Model } from './model';
@@ -53,6 +53,10 @@ export class Collection<
53
53
  return this.options.name;
54
54
  }
55
55
 
56
+ get db() {
57
+ return this.context.database;
58
+ }
59
+
56
60
  constructor(options: CollectionOptions, context?: CollectionContext) {
57
61
  super();
58
62
  this.context = context;
@@ -186,6 +190,26 @@ export class Collection<
186
190
  }
187
191
  }
188
192
 
193
+ remove() {
194
+ this.context.database.removeCollection(this.name);
195
+ }
196
+
197
+ async removeFromDb(options?: QueryInterfaceDropTableOptions) {
198
+ if (
199
+ await this.existsInDb({
200
+ transaction: options?.transaction,
201
+ })
202
+ ) {
203
+ const queryInterface = this.db.sequelize.getQueryInterface();
204
+ await queryInterface.dropTable(this.model.tableName, options);
205
+ }
206
+ this.remove();
207
+ }
208
+
209
+ async existsInDb(options?: Transactionable) {
210
+ return this.db.collectionExistsInDb(this.name, options);
211
+ }
212
+
189
213
  removeField(name) {
190
214
  if (!this.fields.has(name)) {
191
215
  return;
@@ -195,7 +219,7 @@ export class Collection<
195
219
  if (bool) {
196
220
  this.emit('field.afterRemove', field);
197
221
  }
198
- return bool;
222
+ return field as Field;
199
223
  }
200
224
 
201
225
  /**
package/src/database.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { applyMixins, AsyncEmitter } from '@nocobase/utils';
2
2
  import merge from 'deepmerge';
3
3
  import { EventEmitter } from 'events';
4
+ import glob from 'glob';
4
5
  import lodash from 'lodash';
5
- import { isAbsolute, resolve } from 'path';
6
+ import { basename, isAbsolute, resolve } from 'path';
6
7
  import {
7
8
  ModelCtor,
8
9
  Op,
@@ -11,12 +12,15 @@ import {
11
12
  QueryOptions,
12
13
  Sequelize,
13
14
  SyncOptions,
15
+ Transactionable,
14
16
  Utils
15
17
  } from 'sequelize';
18
+ import { SequelizeStorage, Umzug } from 'umzug';
16
19
  import { Collection, CollectionOptions, RepositoryType } from './collection';
17
20
  import { ImporterReader, ImportFileExtension } from './collection-importer';
18
21
  import * as FieldTypes from './fields';
19
22
  import { Field, FieldContext, RelationField } from './fields';
23
+ import { Migrations } from './migration';
20
24
  import { Model } from './model';
21
25
  import { ModelHook } from './model-hook';
22
26
  import extendOperators from './operators';
@@ -36,9 +40,10 @@ interface MapOf<T> {
36
40
 
37
41
  export interface IDatabaseOptions extends Options {
38
42
  tablePrefix?: string;
43
+ migrator?: any;
39
44
  }
40
45
 
41
- export type DatabaseOptions = IDatabaseOptions | Sequelize;
46
+ export type DatabaseOptions = IDatabaseOptions;
42
47
 
43
48
  interface RegisterOperatorsContext {
44
49
  db?: Database;
@@ -51,10 +56,19 @@ export interface CleanOptions extends QueryInterfaceDropAllTablesOptions {
51
56
  drop?: boolean;
52
57
  }
53
58
 
59
+ export type AddMigrationsOptions = {
60
+ context?: any;
61
+ namespace?: string;
62
+ extensions?: string[];
63
+ directory: string;
64
+ };
65
+
54
66
  type OperatorFunc = (value: any, ctx?: RegisterOperatorsContext) => any;
55
67
 
56
68
  export class Database extends EventEmitter implements AsyncEmitter {
57
69
  sequelize: Sequelize;
70
+ migrator: Umzug;
71
+ migrations: Migrations;
58
72
  fieldTypes = new Map();
59
73
  options: IDatabaseOptions;
60
74
  models = new Map<string, ModelCtor<Model>>();
@@ -71,27 +85,30 @@ export class Database extends EventEmitter implements AsyncEmitter {
71
85
  constructor(options: DatabaseOptions) {
72
86
  super();
73
87
 
74
- if (options instanceof Sequelize) {
75
- this.sequelize = options;
76
- } else {
77
- const opts = {
78
- sync: {
79
- alter: {
80
- drop: false,
81
- },
82
- force: false,
88
+ const opts = {
89
+ sync: {
90
+ alter: {
91
+ drop: false,
83
92
  },
84
- ...options,
85
- };
86
- if (options.storage && options.storage !== ':memory:') {
87
- if (!isAbsolute(options.storage)) {
88
- opts.storage = resolve(process.cwd(), options.storage);
89
- }
93
+ force: false,
94
+ },
95
+ ...options,
96
+ };
97
+
98
+ if (options.storage && options.storage !== ':memory:') {
99
+ if (!isAbsolute(options.storage)) {
100
+ opts.storage = resolve(process.cwd(), options.storage);
90
101
  }
91
- this.sequelize = new Sequelize(opts);
92
- this.options = opts;
93
102
  }
94
103
 
104
+ if (options.dialect === 'sqlite') {
105
+ delete opts.timezone;
106
+ } else if (!opts.timezone) {
107
+ opts.timezone = '+00:00';
108
+ }
109
+
110
+ this.sequelize = new Sequelize(opts);
111
+ this.options = opts;
95
112
  this.collections = new Map();
96
113
  this.modelHook = new ModelHook(this);
97
114
 
@@ -116,6 +133,62 @@ export class Database extends EventEmitter implements AsyncEmitter {
116
133
  }
117
134
 
118
135
  this.initOperators();
136
+
137
+ const migratorOptions: any = this.options.migrator || {};
138
+
139
+ const context = {
140
+ db: this,
141
+ sequelize: this.sequelize,
142
+ queryInterface: this.sequelize.getQueryInterface(),
143
+ ...migratorOptions.context,
144
+ };
145
+
146
+ this.migrations = new Migrations(context);
147
+ this.migrator = new Umzug({
148
+ logger: migratorOptions.logger || console,
149
+ migrations: this.migrations.callback(),
150
+ context,
151
+ storage: new SequelizeStorage({
152
+ modelName: `${this.options.tablePrefix || ''}migrations`,
153
+ ...migratorOptions.storage,
154
+ sequelize: this.sequelize,
155
+ }),
156
+ });
157
+ }
158
+
159
+ addMigration(item) {
160
+ return this.migrations.add(item);
161
+ }
162
+
163
+ addMigrations(options: AddMigrationsOptions) {
164
+ const { namespace, context, extensions = ['js', 'ts'], directory } = options;
165
+ const patten = `${directory}/*.{${extensions.join(',')}}`;
166
+ const files = glob.sync(patten, {
167
+ ignore: ['**/*.d.ts'],
168
+ });
169
+ for (const file of files) {
170
+ let filename = basename(file);
171
+ filename = filename.substring(0, filename.lastIndexOf('.')) || filename;
172
+ this.migrations.add({
173
+ name: namespace ? `${namespace}/${filename}` : filename,
174
+ migration: this.requireModule(file),
175
+ context,
176
+ });
177
+ }
178
+ }
179
+
180
+ inDialect(...dialect: string[]) {
181
+ return dialect.includes(this.sequelize.getDialect());
182
+ }
183
+
184
+ private requireModule(module: any) {
185
+ if (typeof module === 'string') {
186
+ module = require(module);
187
+ }
188
+ if (typeof module !== 'object') {
189
+ return module;
190
+ }
191
+ return module.__esModule ? module.default : module;
119
192
  }
120
193
 
121
194
  /**
@@ -161,9 +234,13 @@ export class Database extends EventEmitter implements AsyncEmitter {
161
234
 
162
235
  const result = this.collections.delete(name);
163
236
 
237
+ this.sequelize.modelManager.removeModel(collection.model);
238
+
164
239
  if (result) {
165
240
  this.emit('afterRemoveCollection', collection);
166
241
  }
242
+
243
+ return collection;
167
244
  }
168
245
 
169
246
  getModel<M extends Model>(name: string) {
@@ -268,12 +345,19 @@ export class Database extends EventEmitter implements AsyncEmitter {
268
345
  }
269
346
  }
270
347
 
348
+ async collectionExistsInDb(name, options?: Transactionable) {
349
+ const tables = await this.sequelize.getQueryInterface().showAllTables({
350
+ transaction: options?.transaction,
351
+ });
352
+ return !!tables.find((table) => table === `${this.getTablePrefix()}${name}`);
353
+ }
354
+
271
355
  public isSqliteMemory() {
272
356
  return this.sequelize.getDialect() === 'sqlite' && lodash.get(this.options, 'storage') == ':memory:';
273
357
  }
274
358
 
275
- async auth(options: QueryOptions & { repeat?: number } = {}) {
276
- const { repeat = 10, ...others } = options;
359
+ async auth(options: QueryOptions & { retry?: number } = {}) {
360
+ const { retry = 10, ...others } = options;
277
361
  const delay = (ms) => new Promise((yea) => setTimeout(yea, ms));
278
362
  let count = 1;
279
363
  const authenticate = async () => {
@@ -282,7 +366,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
282
366
  console.log('Connection has been established successfully.');
283
367
  return true;
284
368
  } catch (error) {
285
- if (count >= repeat) {
369
+ if (count >= retry) {
286
370
  throw error;
287
371
  }
288
372
  console.log('reconnecting...', count);
@@ -291,7 +375,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
291
375
  return await authenticate();
292
376
  }
293
377
  };
294
-
295
378
  return await authenticate();
296
379
  }
297
380
 
@@ -322,13 +405,13 @@ export class Database extends EventEmitter implements AsyncEmitter {
322
405
  return this.sequelize.close();
323
406
  }
324
407
 
325
- on(event: string | symbol, listener: (...args: any[]) => void): this {
326
- const modelEventName = this.modelHook.isModelHook(event);
327
-
328
- if (modelEventName && !this.modelHook.hasBindEvent(modelEventName)) {
329
- this.sequelize.addHook(modelEventName, this.modelHook.sequelizeHookBuilder(modelEventName));
408
+ on(event: string | symbol, listener): this {
409
+ // NOTE: to match if event is a sequelize or model type
410
+ const type = this.modelHook.match(event);
330
411
 
331
- this.modelHook.bindEvent(modelEventName);
412
+ if (type && !this.modelHook.hasBoundEvent(type)) {
413
+ this.sequelize.addHook(type, this.modelHook.buildSequelizeHook(type));
414
+ this.modelHook.bindEvent(type);
332
415
  }
333
416
 
334
417
  return super.on(event, listener);
@@ -1,5 +1,12 @@
1
1
  import _ from 'lodash';
2
- import { DataType, ModelAttributeColumnOptions, ModelIndexesOptions, SyncOptions } from 'sequelize';
2
+ import {
3
+ DataType,
4
+ ModelAttributeColumnOptions,
5
+ ModelIndexesOptions,
6
+ QueryInterfaceOptions,
7
+ SyncOptions,
8
+ Transactionable
9
+ } from 'sequelize';
3
10
  import { Collection } from '../collection';
4
11
  import { Database } from '../database';
5
12
 
@@ -75,6 +82,86 @@ export abstract class Field {
75
82
  return this.options[name];
76
83
  }
77
84
 
85
+ remove() {
86
+ return this.collection.removeField(this.name);
87
+ }
88
+
89
+ async removeFromDb(options?: QueryInterfaceOptions) {
90
+ if (!this.collection.model.rawAttributes[this.name]) {
91
+ this.remove();
92
+ // console.log('field is not attribute');
93
+ return;
94
+ }
95
+ if ((this.collection.model as any)._virtualAttributes.has(this.name)) {
96
+ this.remove();
97
+ // console.log('field is virtual attribute');
98
+ return;
99
+ }
100
+ if (this.collection.model.primaryKeyAttributes.includes(this.name)) {
101
+ // 主键不能删除
102
+ return;
103
+ }
104
+ if (this.collection.model.options.timestamps !== false) {
105
+ // timestamps 相关字段不删除
106
+ if (['createdAt', 'updatedAt', 'deletedAt'].includes(this.name)) {
107
+ return;
108
+ }
109
+ }
110
+ // 排序字段通过 sortable 控制
111
+ const sortable = this.collection.options.sortable;
112
+ if (sortable) {
113
+ let sortField: string;
114
+ if (sortable === true) {
115
+ sortField = 'sort';
116
+ } else if (typeof sortable === 'string') {
117
+ sortField = sortable;
118
+ } else if (sortable.name) {
119
+ sortField = sortable.name || 'sort';
120
+ }
121
+ if (this.name === sortField) {
122
+ return;
123
+ }
124
+ }
125
+ if (this.options.field && this.name !== this.options.field) {
126
+ // field 指向的是真实的字段名,如果与 name 不一样,说明字段只是引用
127
+ this.remove();
128
+ return;
129
+ }
130
+ if (
131
+ await this.existsInDb({
132
+ transaction: options?.transaction,
133
+ })
134
+ ) {
135
+ const queryInterface = this.database.sequelize.getQueryInterface();
136
+ await queryInterface.removeColumn(this.collection.model.tableName, this.name, options);
137
+ }
138
+ this.remove();
139
+ }
140
+
141
+ async existsInDb(options?: Transactionable) {
142
+ const opts = {
143
+ transaction: options?.transaction,
144
+ };
145
+ let sql;
146
+ if (this.database.sequelize.getDialect() === 'sqlite') {
147
+ sql = `SELECT * from pragma_table_info('${this.collection.model.tableName}') WHERE name = '${this.name}'`;
148
+ } else if (this.database.inDialect('mysql')) {
149
+ sql = `
150
+ select column_name
151
+ from INFORMATION_SCHEMA.COLUMNS
152
+ where TABLE_SCHEMA='${this.database.options.database}' AND TABLE_NAME='${this.collection.model.tableName}' AND column_name='${this.name}'
153
+ `;
154
+ } else {
155
+ sql = `
156
+ select column_name
157
+ from INFORMATION_SCHEMA.COLUMNS
158
+ where TABLE_NAME='${this.collection.model.tableName}' AND column_name='${this.name}'
159
+ `;
160
+ }
161
+ const [rows] = await this.database.sequelize.query(sql, opts);
162
+ return rows.length > 0;
163
+ }
164
+
78
165
  merge(obj: any) {
79
166
  Object.assign(this.options, obj);
80
167
  }