@nocobase/database 0.9.0-alpha.2 → 0.9.1-alpha.1
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.
- package/lib/collection-importer.js +1 -1
- package/lib/collection.d.ts +7 -1
- package/lib/collection.js +135 -61
- package/lib/database-utils/index.d.ts +8 -0
- package/lib/database-utils/index.js +59 -0
- package/lib/database.d.ts +26 -3
- package/lib/database.js +224 -61
- package/lib/fields/array-field.d.ts +1 -1
- package/lib/fields/array-field.js +2 -2
- package/lib/fields/belongs-to-many-field.js +1 -2
- package/lib/fields/field.d.ts +1 -0
- package/lib/fields/field.js +37 -15
- package/lib/fields/has-one-field.d.ts +1 -1
- package/lib/fields/has-one-field.js +9 -5
- package/lib/fields/number-field.d.ts +9 -6
- package/lib/fields/number-field.js +8 -6
- package/lib/fields/sort-field.js +15 -1
- package/lib/index.d.ts +6 -4
- package/lib/index.js +59 -36
- package/lib/mock-database.d.ts +2 -0
- package/lib/mock-database.js +3 -1
- package/lib/model.js +10 -1
- package/lib/options-parser.js +3 -0
- package/lib/relation-repository/belongs-to-many-repository.js +4 -2
- package/lib/repository.js +5 -2
- package/lib/sync-runner.d.ts +1 -1
- package/lib/sync-runner.js +28 -18
- package/lib/types.d.ts +7 -1
- package/lib/update-associations.js +17 -3
- package/lib/update-guard.d.ts +1 -0
- package/lib/update-guard.js +6 -0
- package/lib/utils.d.ts +5 -0
- package/lib/utils.js +68 -0
- package/lib/value-parsers/array-value-parser.d.ts +8 -0
- package/lib/value-parsers/array-value-parser.js +76 -0
- package/lib/value-parsers/base-value-parser.d.ts +12 -0
- package/lib/value-parsers/base-value-parser.js +59 -0
- package/lib/value-parsers/boolean-value-parser.d.ts +4 -0
- package/lib/value-parsers/boolean-value-parser.js +46 -0
- package/lib/value-parsers/date-value-parser.d.ts +5 -0
- package/lib/value-parsers/date-value-parser.js +91 -0
- package/lib/value-parsers/index.d.ts +12 -0
- package/lib/value-parsers/index.js +102 -0
- package/lib/value-parsers/json-value-parser.d.ts +4 -0
- package/lib/value-parsers/json-value-parser.js +37 -0
- package/lib/value-parsers/number-value-parser.d.ts +4 -0
- package/lib/value-parsers/number-value-parser.js +49 -0
- package/lib/value-parsers/string-value-parser.d.ts +8 -0
- package/lib/value-parsers/string-value-parser.js +76 -0
- package/lib/value-parsers/to-many-value-parser.d.ts +13 -0
- package/lib/value-parsers/to-many-value-parser.js +169 -0
- package/lib/value-parsers/to-one-value-parser.d.ts +4 -0
- package/lib/value-parsers/to-one-value-parser.js +49 -0
- package/package.json +4 -3
- package/src/__tests__/bigint.test.ts +1 -1
- package/src/__tests__/collection-importer.test.ts +13 -1
- package/src/__tests__/collection.test.ts +19 -9
- package/src/__tests__/database.test.ts +32 -0
- package/src/__tests__/fields/sort-field.test.ts +23 -0
- package/src/__tests__/inhertits/collection-inherits.test.ts +7 -5
- package/src/__tests__/percent2float.test.ts +14 -0
- package/src/__tests__/postgres/schema.test.ts +120 -0
- package/src/__tests__/underscored-options.test.ts +207 -0
- package/src/__tests__/update-associations-through.test.ts +73 -0
- package/src/__tests__/value-parsers/base.test.ts +20 -0
- package/src/__tests__/value-parsers/date.test.ts +67 -0
- package/src/__tests__/value-parsers/number.test.ts +46 -0
- package/src/__tests__/value-parsers/to-many.test.ts +206 -0
- package/src/__tests__/value-parsers/to-one.test.ts +60 -0
- package/src/collection-importer.ts +2 -2
- package/src/collection.ts +97 -15
- package/src/database-utils/index.ts +38 -0
- package/src/database.ts +171 -33
- package/src/fields/array-field.ts +1 -1
- package/src/fields/belongs-to-field.ts +1 -1
- package/src/fields/belongs-to-many-field.ts +0 -1
- package/src/fields/field.ts +45 -16
- package/src/fields/has-many-field.ts +1 -1
- package/src/fields/has-one-field.ts +11 -7
- package/src/fields/number-field.ts +10 -6
- package/src/fields/sort-field.ts +13 -1
- package/src/index.ts +7 -4
- package/src/inherited-collection.ts +1 -0
- package/src/mock-database.ts +3 -1
- package/src/model.ts +11 -2
- package/src/options-parser.ts +5 -0
- package/src/relation-repository/belongs-to-many-repository.ts +4 -2
- package/src/repository.ts +8 -3
- package/src/sync-runner.ts +33 -19
- package/src/types.ts +12 -1
- package/src/update-associations.ts +12 -5
- package/src/update-guard.ts +6 -0
- package/src/utils.ts +94 -0
- package/src/value-parsers/array-value-parser.ts +30 -0
- package/src/value-parsers/base-value-parser.ts +40 -0
- package/src/value-parsers/boolean-value-parser.ts +29 -0
- package/src/value-parsers/date-value-parser.ts +38 -0
- package/src/value-parsers/index.ts +46 -0
- package/src/value-parsers/json-value-parser.ts +19 -0
- package/src/value-parsers/number-value-parser.ts +29 -0
- package/src/value-parsers/string-value-parser.ts +31 -0
- package/src/value-parsers/to-many-value-parser.ts +85 -0
- package/src/value-parsers/to-one-value-parser.ts +20 -0
package/src/fields/field.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { Collection } from '../collection';
|
|
|
12
12
|
import { Database } from '../database';
|
|
13
13
|
import { InheritedCollection } from '../inherited-collection';
|
|
14
14
|
import { ModelEventTypes } from '../types';
|
|
15
|
+
import { snakeCase } from '../utils';
|
|
15
16
|
|
|
16
17
|
export interface FieldContext {
|
|
17
18
|
database: Database;
|
|
@@ -39,9 +40,9 @@ export abstract class Field {
|
|
|
39
40
|
[key: string]: any;
|
|
40
41
|
|
|
41
42
|
constructor(options?: any, context?: FieldContext) {
|
|
42
|
-
this.context = context;
|
|
43
|
-
this.database = context.database;
|
|
44
|
-
this.collection = context.collection;
|
|
43
|
+
this.context = context as any;
|
|
44
|
+
this.database = this.context.database;
|
|
45
|
+
this.collection = this.context.collection;
|
|
45
46
|
this.options = options || {};
|
|
46
47
|
this.init();
|
|
47
48
|
}
|
|
@@ -89,6 +90,18 @@ export abstract class Field {
|
|
|
89
90
|
return this.collection.removeField(this.name);
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
columnName() {
|
|
94
|
+
if (this.options.field) {
|
|
95
|
+
return this.options.field;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (this.database.options.underscored) {
|
|
99
|
+
return snakeCase(this.name);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return this.name;
|
|
103
|
+
}
|
|
104
|
+
|
|
92
105
|
async removeFromDb(options?: QueryInterfaceOptions) {
|
|
93
106
|
const attribute = this.collection.model.rawAttributes[this.name];
|
|
94
107
|
|
|
@@ -113,14 +126,19 @@ export abstract class Field {
|
|
|
113
126
|
}
|
|
114
127
|
if (this.collection.model.options.timestamps !== false) {
|
|
115
128
|
// timestamps 相关字段不删除
|
|
116
|
-
|
|
129
|
+
let timestampsFields = ['createdAt', 'updatedAt', 'deletedAt'];
|
|
130
|
+
if (this.database.options.underscored) {
|
|
131
|
+
timestampsFields = timestampsFields.map((field) => snakeCase(field));
|
|
132
|
+
}
|
|
133
|
+
if (timestampsFields.includes(this.columnName())) {
|
|
134
|
+
this.collection.fields.delete(this.name);
|
|
117
135
|
return;
|
|
118
136
|
}
|
|
119
137
|
}
|
|
120
138
|
// 排序字段通过 sortable 控制
|
|
121
139
|
const sortable = this.collection.options.sortable;
|
|
122
140
|
if (sortable) {
|
|
123
|
-
let sortField:
|
|
141
|
+
let sortField: any;
|
|
124
142
|
if (sortable === true) {
|
|
125
143
|
sortField = 'sort';
|
|
126
144
|
} else if (typeof sortable === 'string') {
|
|
@@ -132,19 +150,28 @@ export abstract class Field {
|
|
|
132
150
|
return;
|
|
133
151
|
}
|
|
134
152
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
153
|
+
|
|
154
|
+
// if (this.options.field && this.name !== this.options.field) {
|
|
155
|
+
// // field 指向的是真实的字段名,如果与 name 不一样,说明字段只是引用
|
|
156
|
+
// this.remove();
|
|
157
|
+
// return;
|
|
158
|
+
// }
|
|
159
|
+
|
|
160
|
+
const columnReferencesCount = _.filter(
|
|
161
|
+
this.collection.model.rawAttributes,
|
|
162
|
+
(attr) => attr.field == this.columnName(),
|
|
163
|
+
).length;
|
|
164
|
+
|
|
140
165
|
if (
|
|
141
|
-
await this.existsInDb({
|
|
166
|
+
(await this.existsInDb({
|
|
142
167
|
transaction: options?.transaction,
|
|
143
|
-
})
|
|
168
|
+
})) &&
|
|
169
|
+
columnReferencesCount == 1
|
|
144
170
|
) {
|
|
145
171
|
const queryInterface = this.database.sequelize.getQueryInterface();
|
|
146
|
-
await queryInterface.removeColumn(this.collection.model.tableName, this.
|
|
172
|
+
await queryInterface.removeColumn(this.collection.model.tableName, this.columnName(), options);
|
|
147
173
|
}
|
|
174
|
+
|
|
148
175
|
this.remove();
|
|
149
176
|
}
|
|
150
177
|
|
|
@@ -154,18 +181,20 @@ export abstract class Field {
|
|
|
154
181
|
};
|
|
155
182
|
let sql;
|
|
156
183
|
if (this.database.sequelize.getDialect() === 'sqlite') {
|
|
157
|
-
sql = `SELECT * from pragma_table_info('${this.collection.model.tableName}') WHERE name = '${this.
|
|
184
|
+
sql = `SELECT * from pragma_table_info('${this.collection.model.tableName}') WHERE name = '${this.columnName()}'`;
|
|
158
185
|
} else if (this.database.inDialect('mysql')) {
|
|
159
186
|
sql = `
|
|
160
187
|
select column_name
|
|
161
188
|
from INFORMATION_SCHEMA.COLUMNS
|
|
162
|
-
where TABLE_SCHEMA='${this.database.options.database}' AND TABLE_NAME='${
|
|
189
|
+
where TABLE_SCHEMA='${this.database.options.database}' AND TABLE_NAME='${
|
|
190
|
+
this.collection.model.tableName
|
|
191
|
+
}' AND column_name='${this.columnName()}'
|
|
163
192
|
`;
|
|
164
193
|
} else {
|
|
165
194
|
sql = `
|
|
166
195
|
select column_name
|
|
167
196
|
from INFORMATION_SCHEMA.COLUMNS
|
|
168
|
-
where TABLE_NAME='${this.collection.model.tableName}' AND column_name='${this.
|
|
197
|
+
where TABLE_NAME='${this.collection.model.tableName}' AND column_name='${this.columnName()}'
|
|
169
198
|
`;
|
|
170
199
|
}
|
|
171
200
|
const [rows] = await this.database.sequelize.query(sql, opts);
|
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
ForeignKeyOptions,
|
|
6
6
|
HasOneOptions,
|
|
7
7
|
HasOneOptions as SequelizeHasOneOptions,
|
|
8
|
-
Utils
|
|
8
|
+
Utils
|
|
9
9
|
} from 'sequelize';
|
|
10
10
|
import { Collection } from '../collection';
|
|
11
|
+
import { Reference } from '../features/ReferencesMap';
|
|
11
12
|
import { checkIdentifier } from '../utils';
|
|
12
13
|
import { BaseRelationFieldOptions, RelationField } from './relation-field';
|
|
13
|
-
import { Reference } from '../features/ReferencesMap';
|
|
14
14
|
|
|
15
15
|
export interface HasOneFieldOptions extends HasOneOptions {
|
|
16
16
|
/**
|
|
@@ -84,11 +84,15 @@ export class HasOneField extends RelationField {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
get foreignKey() {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
const foreignKey = (() => {
|
|
88
|
+
if (this.options.foreignKey) {
|
|
89
|
+
return this.options.foreignKey;
|
|
90
|
+
}
|
|
91
|
+
const { model } = this.context.collection;
|
|
92
|
+
return Utils.camelize([model.options.name.singular, model.primaryKeyAttribute].join('_'));
|
|
93
|
+
})();
|
|
94
|
+
|
|
95
|
+
return foreignKey;
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
reference(association): Reference {
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { DataTypes } from 'sequelize';
|
|
2
2
|
import { BaseColumnFieldOptions, Field } from './field';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
abstract class NumberField extends Field {
|
|
5
|
+
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class IntegerField extends NumberField {
|
|
5
9
|
get dataType() {
|
|
6
10
|
return DataTypes.INTEGER;
|
|
7
11
|
}
|
|
@@ -11,7 +15,7 @@ export interface IntegerFieldOptions extends BaseColumnFieldOptions {
|
|
|
11
15
|
type: 'integer';
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
export class BigIntField extends
|
|
18
|
+
export class BigIntField extends NumberField {
|
|
15
19
|
get dataType() {
|
|
16
20
|
return DataTypes.BIGINT;
|
|
17
21
|
}
|
|
@@ -21,7 +25,7 @@ export interface BigIntFieldOptions extends BaseColumnFieldOptions {
|
|
|
21
25
|
type: 'bigInt';
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
export class FloatField extends
|
|
28
|
+
export class FloatField extends NumberField {
|
|
25
29
|
get dataType() {
|
|
26
30
|
return DataTypes.FLOAT;
|
|
27
31
|
}
|
|
@@ -31,7 +35,7 @@ export interface FloatFieldOptions extends BaseColumnFieldOptions {
|
|
|
31
35
|
type: 'float';
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
export class DoubleField extends
|
|
38
|
+
export class DoubleField extends NumberField {
|
|
35
39
|
get dataType() {
|
|
36
40
|
return DataTypes.DOUBLE;
|
|
37
41
|
}
|
|
@@ -41,7 +45,7 @@ export interface DoubleFieldOptions extends BaseColumnFieldOptions {
|
|
|
41
45
|
type: 'double';
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
export class RealField extends
|
|
48
|
+
export class RealField extends NumberField {
|
|
45
49
|
get dataType() {
|
|
46
50
|
return DataTypes.REAL;
|
|
47
51
|
}
|
|
@@ -51,7 +55,7 @@ export interface RealFieldOptions extends BaseColumnFieldOptions {
|
|
|
51
55
|
type: 'real';
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
export class DecimalField extends
|
|
58
|
+
export class DecimalField extends NumberField {
|
|
55
59
|
get dataType() {
|
|
56
60
|
return DataTypes.DECIMAL;
|
|
57
61
|
}
|
package/src/fields/sort-field.ts
CHANGED
|
@@ -53,9 +53,21 @@ export class SortField extends Field {
|
|
|
53
53
|
transaction,
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
const orderKey = (() => {
|
|
57
|
+
const model = this.collection.model;
|
|
58
|
+
if (model.primaryKeyAttribute) {
|
|
59
|
+
return model.primaryKeyAttribute;
|
|
60
|
+
}
|
|
61
|
+
if (model.rawAttributes['createdAt']) {
|
|
62
|
+
return 'createdAt';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
throw new Error(`can not find order key for collection ${this.collection.name}`);
|
|
66
|
+
})();
|
|
67
|
+
|
|
56
68
|
if (emptyCount === totalCount && emptyCount > 0) {
|
|
57
69
|
const records = await this.collection.repository.find({
|
|
58
|
-
order: [
|
|
70
|
+
order: [orderKey],
|
|
59
71
|
transaction,
|
|
60
72
|
});
|
|
61
73
|
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
export { DataTypes, ModelStatic, Op, SyncOptions } from 'sequelize';
|
|
2
2
|
export * from './collection';
|
|
3
|
-
export * from './
|
|
3
|
+
export * from './collection-importer';
|
|
4
4
|
export * from './database';
|
|
5
5
|
export { Database as default } from './database';
|
|
6
|
+
export * from './field-repository/array-field-repository';
|
|
6
7
|
export * from './fields';
|
|
8
|
+
export * from './filter-match';
|
|
9
|
+
export * from './inherited-collection';
|
|
7
10
|
export * from './magic-attribute-model';
|
|
8
11
|
export * from './migration';
|
|
9
12
|
export * from './mock-database';
|
|
@@ -15,6 +18,6 @@ export * from './relation-repository/multiple-relation-repository';
|
|
|
15
18
|
export * from './relation-repository/single-relation-repository';
|
|
16
19
|
export * from './repository';
|
|
17
20
|
export * from './update-associations';
|
|
18
|
-
export
|
|
19
|
-
export * from './
|
|
20
|
-
|
|
21
|
+
export { snakeCase } from './utils';
|
|
22
|
+
export * from './value-parsers';
|
|
23
|
+
|
|
@@ -4,6 +4,7 @@ import { Field } from '.';
|
|
|
4
4
|
|
|
5
5
|
export class InheritedCollection extends Collection {
|
|
6
6
|
parents?: Collection[];
|
|
7
|
+
|
|
7
8
|
constructor(options: CollectionOptions, context: CollectionContext) {
|
|
8
9
|
if (!options.inherits) {
|
|
9
10
|
throw new Error('InheritedCollection must have inherits option');
|
package/src/mock-database.ts
CHANGED
|
@@ -31,10 +31,12 @@ export function getConfigByEnv() {
|
|
|
31
31
|
collate: 'utf8mb4_unicode_ci',
|
|
32
32
|
},
|
|
33
33
|
timezone: process.env.DB_TIMEZONE,
|
|
34
|
+
underscored: process.env.DB_UNDERSCORED === 'true',
|
|
35
|
+
schema: process.env.DB_SCHEMA !== 'public' ? process.env.DB_SCHEMA : undefined,
|
|
34
36
|
};
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
export function mockDatabase(options: IDatabaseOptions = {}): MockDatabase {
|
|
38
|
-
const dbOptions = merge(getConfigByEnv(), options);
|
|
40
|
+
const dbOptions = merge(getConfigByEnv(), options) as any;
|
|
39
41
|
return new MockDatabase(dbOptions);
|
|
40
42
|
}
|
package/src/model.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import lodash from 'lodash';
|
|
2
|
-
import {
|
|
2
|
+
import { Model as SequelizeModel, ModelStatic } from 'sequelize';
|
|
3
3
|
import { Collection } from './collection';
|
|
4
4
|
import { Database } from './database';
|
|
5
5
|
import { Field } from './fields';
|
|
6
|
-
import type { InheritedCollection } from './inherited-collection';
|
|
7
6
|
import { SyncRunner } from './sync-runner';
|
|
8
7
|
|
|
9
8
|
const _ = lodash;
|
|
@@ -28,6 +27,7 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
|
|
|
28
27
|
public static collection: Collection;
|
|
29
28
|
|
|
30
29
|
[key: string]: any;
|
|
30
|
+
|
|
31
31
|
protected _changedWithAssociations = new Set();
|
|
32
32
|
protected _previousDataValuesWithAssociations = {};
|
|
33
33
|
|
|
@@ -153,6 +153,15 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
|
|
|
153
153
|
static async sync(options) {
|
|
154
154
|
const model = this as any;
|
|
155
155
|
|
|
156
|
+
const _schema = model._schema;
|
|
157
|
+
|
|
158
|
+
if (_schema && _schema != 'public') {
|
|
159
|
+
await this.sequelize.query(`CREATE SCHEMA IF NOT EXISTS "${_schema}";`, {
|
|
160
|
+
raw: true,
|
|
161
|
+
transaction: options?.transaction,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
156
165
|
// fix sequelize sync with model that not have any column
|
|
157
166
|
if (Object.keys(model.tableAttributes).length === 0) {
|
|
158
167
|
if (this.database.inDialect('sqlite', 'mysql')) {
|
package/src/options-parser.ts
CHANGED
|
@@ -75,6 +75,7 @@ export class OptionsParser {
|
|
|
75
75
|
for (const sortKey of sort) {
|
|
76
76
|
let direction = sortKey.startsWith('-') ? 'DESC' : 'ASC';
|
|
77
77
|
let sortField: Array<any> = sortKey.replace('-', '').split('.');
|
|
78
|
+
|
|
78
79
|
if (this.database.inDialect('postgres', 'sqlite')) {
|
|
79
80
|
direction = `${direction} NULLS LAST`;
|
|
80
81
|
}
|
|
@@ -86,7 +87,11 @@ export class OptionsParser {
|
|
|
86
87
|
sortField[i] = associationModel.associations[associationKey].target;
|
|
87
88
|
associationModel = sortField[i];
|
|
88
89
|
}
|
|
90
|
+
} else {
|
|
91
|
+
const rawField = this.model.rawAttributes[sortField[0]];
|
|
92
|
+
sortField[0] = rawField?.field || sortField[0];
|
|
89
93
|
}
|
|
94
|
+
|
|
90
95
|
sortField.push(direction);
|
|
91
96
|
if (this.database.inDialect('mysql')) {
|
|
92
97
|
orderParams.push([Sequelize.fn('ISNULL', Sequelize.col(`${this.model.name}.${sortField[0]}`))]);
|
|
@@ -62,6 +62,8 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
|
|
62
62
|
const transaction = await this.getTransaction(options);
|
|
63
63
|
const association = <BelongsToMany>this.association;
|
|
64
64
|
|
|
65
|
+
const throughModel = this.throughModel();
|
|
66
|
+
|
|
65
67
|
const instancesToIds = (instances) => {
|
|
66
68
|
return instances.map((instance) => instance.get(this.targetKey()));
|
|
67
69
|
};
|
|
@@ -69,7 +71,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
|
|
69
71
|
// Through Table
|
|
70
72
|
const throughTableWhere: Array<any> = [
|
|
71
73
|
{
|
|
72
|
-
[association.foreignKey]: this.sourceKeyValue,
|
|
74
|
+
[throughModel.rawAttributes[association.foreignKey].field]: this.sourceKeyValue,
|
|
73
75
|
},
|
|
74
76
|
];
|
|
75
77
|
|
|
@@ -100,7 +102,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
|
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
throughTableWhere.push({
|
|
103
|
-
[association.otherKey]: {
|
|
105
|
+
[throughModel.rawAttributes[association.otherKey].field]: {
|
|
104
106
|
[Op.in]: ids,
|
|
105
107
|
},
|
|
106
108
|
});
|
package/src/repository.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
Op,
|
|
12
12
|
Transactionable,
|
|
13
13
|
UpdateOptions as SequelizeUpdateOptions,
|
|
14
|
-
WhereOperators
|
|
14
|
+
WhereOperators,
|
|
15
15
|
} from 'sequelize';
|
|
16
16
|
import { Collection } from './collection';
|
|
17
17
|
import { Database } from './database';
|
|
@@ -407,7 +407,12 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|
|
407
407
|
|
|
408
408
|
const transaction = await this.getTransaction(options);
|
|
409
409
|
|
|
410
|
-
const guard = UpdateGuard.fromOptions(this.model, {
|
|
410
|
+
const guard = UpdateGuard.fromOptions(this.model, {
|
|
411
|
+
...options,
|
|
412
|
+
action: 'create',
|
|
413
|
+
underscored: this.collection.options.underscored,
|
|
414
|
+
});
|
|
415
|
+
|
|
411
416
|
const values = guard.sanitize(options.values || {});
|
|
412
417
|
|
|
413
418
|
const instance = await this.model.create<any>(values, {
|
|
@@ -476,7 +481,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
|
|
|
476
481
|
}
|
|
477
482
|
const transaction = await this.getTransaction(options);
|
|
478
483
|
|
|
479
|
-
const guard = UpdateGuard.fromOptions(this.model, options);
|
|
484
|
+
const guard = UpdateGuard.fromOptions(this.model, { ...options, underscored: this.collection.options.underscored });
|
|
480
485
|
|
|
481
486
|
const values = guard.sanitize(options.values);
|
|
482
487
|
|
package/src/sync-runner.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { InheritedCollection } from './inherited-collection';
|
|
2
2
|
import lodash from 'lodash';
|
|
3
|
-
import { Sequelize } from 'sequelize';
|
|
4
3
|
|
|
5
4
|
export class SyncRunner {
|
|
6
5
|
static async syncInheritModel(model: any, options: any) {
|
|
@@ -8,6 +7,8 @@ export class SyncRunner {
|
|
|
8
7
|
|
|
9
8
|
const inheritedCollection = model.collection as InheritedCollection;
|
|
10
9
|
const db = inheritedCollection.context.database;
|
|
10
|
+
const schemaName = db.options.schema || 'public';
|
|
11
|
+
|
|
11
12
|
const dialect = db.sequelize.getDialect();
|
|
12
13
|
|
|
13
14
|
const queryInterface = db.sequelize.getQueryInterface();
|
|
@@ -28,7 +29,10 @@ export class SyncRunner {
|
|
|
28
29
|
|
|
29
30
|
const parentTables = parents.map((parent) => parent.model.tableName);
|
|
30
31
|
|
|
31
|
-
const tableName = model.
|
|
32
|
+
const tableName = model.tableName;
|
|
33
|
+
|
|
34
|
+
const schemaTableName = db.utils.addSchema(tableName);
|
|
35
|
+
const quoteTableName = db.utils.quoteTable(tableName);
|
|
32
36
|
|
|
33
37
|
const attributes = model.tableAttributes;
|
|
34
38
|
|
|
@@ -42,8 +46,11 @@ export class SyncRunner {
|
|
|
42
46
|
if (childAttributes.id && childAttributes.id.autoIncrement) {
|
|
43
47
|
for (const parent of parentTables) {
|
|
44
48
|
const sequenceNameResult = await queryInterface.sequelize.query(
|
|
45
|
-
`SELECT column_default
|
|
46
|
-
|
|
49
|
+
`SELECT column_default
|
|
50
|
+
FROM information_schema.columns
|
|
51
|
+
WHERE table_name = '${parent}'
|
|
52
|
+
and table_schema = '${schemaName}'
|
|
53
|
+
and "column_name" = 'id';`,
|
|
47
54
|
{
|
|
48
55
|
transaction,
|
|
49
56
|
},
|
|
@@ -59,13 +66,14 @@ export class SyncRunner {
|
|
|
59
66
|
throw new Error(`Can't find sequence name of ${parent}`);
|
|
60
67
|
}
|
|
61
68
|
|
|
62
|
-
const regex = new RegExp(/nextval\('(
|
|
69
|
+
const regex = new RegExp(/nextval\('(.*)'::regclass\)/);
|
|
63
70
|
const match = regex.exec(columnDefault);
|
|
64
71
|
|
|
65
72
|
const sequenceName = match[1];
|
|
66
73
|
|
|
67
74
|
const sequenceCurrentValResult = await queryInterface.sequelize.query(
|
|
68
|
-
`select last_value
|
|
75
|
+
`select last_value
|
|
76
|
+
from ${sequenceName}`,
|
|
69
77
|
{
|
|
70
78
|
transaction,
|
|
71
79
|
},
|
|
@@ -80,26 +88,25 @@ export class SyncRunner {
|
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
90
|
|
|
83
|
-
await this.createTable(
|
|
91
|
+
await this.createTable(schemaTableName, childAttributes, options, model, parentTables, db);
|
|
84
92
|
|
|
85
93
|
if (maxSequenceName) {
|
|
86
94
|
const parentsDeep = Array.from(db.inheritanceMap.getParents(inheritedCollection.name)).map(
|
|
87
95
|
(parent) => db.getCollection(parent).model.tableName,
|
|
88
96
|
);
|
|
89
97
|
|
|
90
|
-
const sequenceTables = [...parentsDeep, tableName];
|
|
98
|
+
const sequenceTables = [...parentsDeep, tableName.toString()];
|
|
91
99
|
|
|
92
100
|
for (const sequenceTable of sequenceTables) {
|
|
93
|
-
const queryName =
|
|
101
|
+
const queryName =
|
|
102
|
+
Boolean(sequenceTable.match(/[A-Z]/)) && !sequenceTable.includes(`"`) ? `"${sequenceTable}"` : sequenceTable;
|
|
94
103
|
|
|
95
104
|
const idColumnQuery = await queryInterface.sequelize.query(
|
|
96
105
|
`
|
|
97
|
-
SELECT
|
|
98
|
-
FROM
|
|
99
|
-
WHERE
|
|
100
|
-
|
|
101
|
-
AND NOT attisdropped
|
|
102
|
-
`,
|
|
106
|
+
SELECT column_name
|
|
107
|
+
FROM information_schema.columns
|
|
108
|
+
WHERE table_name='${queryName}' and column_name='id' and table_schema = '${schemaName}';
|
|
109
|
+
`,
|
|
103
110
|
{
|
|
104
111
|
transaction,
|
|
105
112
|
},
|
|
@@ -110,7 +117,8 @@ AND NOT attisdropped
|
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
await queryInterface.sequelize.query(
|
|
113
|
-
`alter table "${
|
|
120
|
+
`alter table "${schemaName}"."${sequenceTable}"
|
|
121
|
+
alter column id set default nextval('${maxSequenceName}')`,
|
|
114
122
|
{
|
|
115
123
|
transaction,
|
|
116
124
|
},
|
|
@@ -121,7 +129,9 @@ AND NOT attisdropped
|
|
|
121
129
|
if (options.alter) {
|
|
122
130
|
const columns = await queryInterface.describeTable(tableName, options);
|
|
123
131
|
|
|
124
|
-
for (const
|
|
132
|
+
for (const attribute in childAttributes) {
|
|
133
|
+
const columnName = childAttributes[attribute].field;
|
|
134
|
+
|
|
125
135
|
if (!columns[columnName]) {
|
|
126
136
|
await queryInterface.addColumn(tableName, columnName, childAttributes[columnName], options);
|
|
127
137
|
}
|
|
@@ -129,7 +139,7 @@ AND NOT attisdropped
|
|
|
129
139
|
}
|
|
130
140
|
}
|
|
131
141
|
|
|
132
|
-
static async createTable(tableName, attributes, options, model, parentTables) {
|
|
142
|
+
static async createTable(tableName, attributes, options, model, parentTables, db) {
|
|
133
143
|
let sql = '';
|
|
134
144
|
|
|
135
145
|
options = { ...options };
|
|
@@ -154,7 +164,11 @@ AND NOT attisdropped
|
|
|
154
164
|
|
|
155
165
|
sql = `${queryGenerator.createTableQuery(tableName, attributes, options)}`.replace(
|
|
156
166
|
';',
|
|
157
|
-
` INHERITS (${parentTables
|
|
167
|
+
` INHERITS (${parentTables
|
|
168
|
+
.map((t) => {
|
|
169
|
+
return db.utils.quoteTable(db.utils.addSchema(t, db.options.schema));
|
|
170
|
+
})
|
|
171
|
+
.join(', ')});`,
|
|
158
172
|
);
|
|
159
173
|
|
|
160
174
|
return await model.sequelize.query(sql, options);
|
package/src/types.ts
CHANGED
|
@@ -16,6 +16,10 @@ export type ModelCreateWithAssociationsEventType = 'afterCreateWithAssociations'
|
|
|
16
16
|
export type ModelUpdateWithAssociationsEventType = 'afterUpdateWithAssociations';
|
|
17
17
|
export type ModelSaveWithAssociationsEventType = 'afterSaveWithAssociations';
|
|
18
18
|
|
|
19
|
+
export type ModelBulkCreateEvnetType = 'beforeBulkCreate' | 'afterBulkCreate';
|
|
20
|
+
export type ModelBulkUpdateEvnetType = 'beforeBulkUpdate' | 'afterBulkUpdate';
|
|
21
|
+
export type ModelBulkDestroyEvnetType = 'beforeBulkDestroy' | 'afterBulkDestroy';
|
|
22
|
+
|
|
19
23
|
export type ModelValidateEventTypes = ModelValidateEventType | `${CollectionNameType}.${ModelValidateEventType}`;
|
|
20
24
|
export type ModelCreateEventTypes = ModelCreateEventType | `${CollectionNameType}.${ModelCreateEventType}`;
|
|
21
25
|
export type ModelUpdateEventTypes = ModelUpdateEventType | `${CollectionNameType}.${ModelUpdateEventType}`;
|
|
@@ -25,6 +29,10 @@ export type ModelCreateWithAssociationsEventTypes = ModelCreateWithAssociationsE
|
|
|
25
29
|
export type ModelUpdateWithAssociationsEventTypes = ModelUpdateWithAssociationsEventType | `${CollectionNameType}.${ModelUpdateWithAssociationsEventType}`;
|
|
26
30
|
export type ModelSaveWithAssociationsEventTypes = ModelSaveWithAssociationsEventType | `${CollectionNameType}.${ModelSaveWithAssociationsEventType}`;
|
|
27
31
|
|
|
32
|
+
export type ModelBulkCreateEvnetTypes = ModelBulkCreateEvnetType | `${CollectionNameType}.${ModelBulkCreateEvnetType}`;
|
|
33
|
+
export type ModelBulkUpdateEvnetTypes = ModelBulkUpdateEvnetType | `${CollectionNameType}.${ModelBulkUpdateEvnetType}`;
|
|
34
|
+
export type ModelBulkDestroyEvnetTypes = ModelBulkDestroyEvnetType | `${CollectionNameType}.${ModelBulkDestroyEvnetType}`;
|
|
35
|
+
|
|
28
36
|
export type ModelEventTypes = ModelSyncEventType
|
|
29
37
|
| ModelValidateEventTypes
|
|
30
38
|
| ModelCreateEventTypes
|
|
@@ -33,7 +41,10 @@ export type ModelEventTypes = ModelSyncEventType
|
|
|
33
41
|
| ModelDestroyEventTypes
|
|
34
42
|
| ModelCreateWithAssociationsEventTypes
|
|
35
43
|
| ModelUpdateWithAssociationsEventTypes
|
|
36
|
-
| ModelSaveWithAssociationsEventTypes
|
|
44
|
+
| ModelSaveWithAssociationsEventTypes
|
|
45
|
+
| ModelBulkCreateEvnetTypes
|
|
46
|
+
| ModelBulkUpdateEvnetTypes
|
|
47
|
+
| ModelBulkDestroyEvnetTypes;
|
|
37
48
|
|
|
38
49
|
export type DatabaseBeforeDefineCollectionEventType = 'beforeDefineCollection';
|
|
39
50
|
export type DatabaseAfterDefineCollectionEventType = 'afterDefineCollection';
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
HasOne,
|
|
7
7
|
Hookable,
|
|
8
8
|
ModelStatic,
|
|
9
|
-
Transactionable
|
|
9
|
+
Transactionable
|
|
10
10
|
} from 'sequelize';
|
|
11
11
|
import { Model } from './model';
|
|
12
12
|
import { UpdateGuard } from './update-guard';
|
|
@@ -392,7 +392,7 @@ export async function updateMultipleAssociation(
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
if (isStringOrNumber(value)) {
|
|
395
|
-
await model[setAccessor](value, { transaction, context });
|
|
395
|
+
await model[setAccessor](value, { transaction, context, individualHooks: true });
|
|
396
396
|
return;
|
|
397
397
|
}
|
|
398
398
|
|
|
@@ -402,6 +402,7 @@ export async function updateMultipleAssociation(
|
|
|
402
402
|
|
|
403
403
|
const list1 = []; // to be setted
|
|
404
404
|
const list2 = []; // to be added
|
|
405
|
+
const created = [];
|
|
405
406
|
for (const item of value) {
|
|
406
407
|
if (isUndefinedOrNull(item)) {
|
|
407
408
|
continue;
|
|
@@ -413,12 +414,17 @@ export async function updateMultipleAssociation(
|
|
|
413
414
|
} else if (item.sequelize) {
|
|
414
415
|
list1.push(item);
|
|
415
416
|
} else if (typeof item === 'object') {
|
|
417
|
+
const targetKey = (association as any).targetKey || 'id';
|
|
418
|
+
if (item[targetKey]) {
|
|
419
|
+
created.push(item[targetKey]);
|
|
420
|
+
list1.push(item[targetKey]);
|
|
421
|
+
}
|
|
416
422
|
list2.push(item);
|
|
417
423
|
}
|
|
418
424
|
}
|
|
419
425
|
|
|
420
426
|
// associate targets in lists1
|
|
421
|
-
await model[setAccessor](list1, { transaction, context });
|
|
427
|
+
await model[setAccessor](list1, { transaction, context, individualHooks: true });
|
|
422
428
|
|
|
423
429
|
const list3 = [];
|
|
424
430
|
for (const item of list2) {
|
|
@@ -456,8 +462,9 @@ export async function updateMultipleAssociation(
|
|
|
456
462
|
continue;
|
|
457
463
|
}
|
|
458
464
|
const addAccessor = association.accessors.add;
|
|
459
|
-
|
|
460
|
-
|
|
465
|
+
if (!created.includes(item[pk])) {
|
|
466
|
+
await model[addAccessor](item[pk], accessorOptions);
|
|
467
|
+
}
|
|
461
468
|
if (!recursive) {
|
|
462
469
|
continue;
|
|
463
470
|
}
|
package/src/update-guard.ts
CHANGED
|
@@ -13,6 +13,7 @@ type UpdateAction = 'create' | 'update';
|
|
|
13
13
|
export class UpdateGuard {
|
|
14
14
|
model: ModelStatic<any>;
|
|
15
15
|
action: UpdateAction;
|
|
16
|
+
underscored: boolean;
|
|
16
17
|
private associationKeysToBeUpdate: AssociationKeysToBeUpdate;
|
|
17
18
|
private blackList: BlackList;
|
|
18
19
|
private whiteList: WhiteList;
|
|
@@ -162,6 +163,11 @@ export class UpdateGuard {
|
|
|
162
163
|
guard.setBlackList(options.blacklist);
|
|
163
164
|
guard.setAction(lodash.get(options, 'action', 'update'));
|
|
164
165
|
guard.setAssociationKeysToBeUpdate(options.updateAssociationValues);
|
|
166
|
+
|
|
167
|
+
if (options.underscored) {
|
|
168
|
+
guard.underscored = options.underscored;
|
|
169
|
+
}
|
|
170
|
+
|
|
165
171
|
return guard;
|
|
166
172
|
}
|
|
167
173
|
}
|