@nocobase/database 0.9.0-alpha.2 → 0.9.1-alpha.2
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 +9 -2
- package/lib/collection.js +153 -63
- package/lib/database-utils/index.d.ts +8 -0
- package/lib/database-utils/index.js +59 -0
- package/lib/database.d.ts +32 -3
- package/lib/database.js +237 -64
- 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 +8 -3
- 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/filter-parser.js +1 -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/query-interface/mysql-query-interface.d.ts +7 -0
- package/lib/query-interface/mysql-query-interface.js +39 -0
- package/lib/query-interface/postgres-query-interface.d.ts +6 -0
- package/lib/query-interface/postgres-query-interface.js +41 -0
- package/lib/query-interface/query-interface-builder.d.ts +2 -0
- package/lib/query-interface/query-interface-builder.js +23 -0
- package/lib/query-interface/query-interface.d.ts +9 -0
- package/lib/query-interface/query-interface.js +18 -0
- package/lib/query-interface/sqlite-query-interface.d.ts +6 -0
- package/lib/query-interface/sqlite-query-interface.js +38 -0
- package/lib/relation-repository/belongs-to-many-repository.js +4 -2
- package/lib/relation-repository/multiple-relation-repository.js +2 -0
- package/lib/relation-repository/single-relation-repository.js +1 -0
- package/lib/repository.js +5 -2
- package/lib/sync-runner.d.ts +1 -1
- package/lib/sync-runner.js +29 -22
- 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 +78 -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 +5 -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__/filter.test.ts +60 -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 +115 -17
- package/src/database-utils/index.ts +38 -0
- package/src/database.ts +188 -36
- 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 +8 -3
- package/src/fields/field.ts +48 -17
- 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/filter-parser.ts +1 -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/query-interface/mysql-query-interface.ts +20 -0
- package/src/query-interface/postgres-query-interface.ts +22 -0
- package/src/query-interface/query-interface-builder.ts +14 -0
- package/src/query-interface/query-interface.ts +12 -0
- package/src/query-interface/sqlite-query-interface.ts +18 -0
- package/src/relation-repository/belongs-to-many-repository.ts +4 -2
- package/src/relation-repository/multiple-relation-repository.ts +4 -0
- package/src/relation-repository/single-relation-repository.ts +2 -0
- package/src/repository.ts +8 -3
- package/src/sync-runner.ts +35 -24
- package/src/types.ts +12 -1
- package/src/update-associations.ts +12 -5
- package/src/update-guard.ts +6 -0
- package/src/utils.ts +95 -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/filter-parser.ts
CHANGED
|
@@ -178,7 +178,7 @@ export default class FilterParser {
|
|
|
178
178
|
origins.push(attr);
|
|
179
179
|
// if it is target model attribute
|
|
180
180
|
if (target.rawAttributes[attr]) {
|
|
181
|
-
associationKeys.push(attr);
|
|
181
|
+
associationKeys.push(target.rawAttributes[attr].field || attr);
|
|
182
182
|
target = null;
|
|
183
183
|
} else if (target.associations[attr]) {
|
|
184
184
|
// if it is target model association (nested association filter)
|
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]}`))]);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import QueryInterface from './query-interface';
|
|
2
|
+
import { Collection } from '../collection';
|
|
3
|
+
import { Transactionable } from 'sequelize';
|
|
4
|
+
|
|
5
|
+
export default class MysqlQueryInterface extends QueryInterface {
|
|
6
|
+
constructor(db) {
|
|
7
|
+
super(db);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async collectionTableExists(collection: Collection, options?: Transactionable) {
|
|
11
|
+
const transaction = options?.transaction;
|
|
12
|
+
|
|
13
|
+
const tableName = collection.model.tableName;
|
|
14
|
+
const databaseName = this.db.options.database;
|
|
15
|
+
const sql = `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '${databaseName}' AND TABLE_NAME = '${tableName}'`;
|
|
16
|
+
|
|
17
|
+
const results = await this.db.sequelize.query(sql, { type: 'SELECT', transaction });
|
|
18
|
+
return results.length > 0;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import QueryInterface from './query-interface';
|
|
2
|
+
import { Collection } from '../collection';
|
|
3
|
+
|
|
4
|
+
export default class PostgresQueryInterface extends QueryInterface {
|
|
5
|
+
constructor(db) {
|
|
6
|
+
super(db);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async collectionTableExists(collection: Collection, options?) {
|
|
10
|
+
const transaction = options?.transaction;
|
|
11
|
+
|
|
12
|
+
const tableName = collection.model.tableName;
|
|
13
|
+
const schema = collection.collectionSchema() || 'public';
|
|
14
|
+
|
|
15
|
+
const sql = `SELECT EXISTS(SELECT 1 FROM information_schema.tables
|
|
16
|
+
WHERE table_schema = '${schema}'
|
|
17
|
+
AND table_name = '${tableName}')`;
|
|
18
|
+
|
|
19
|
+
const results = await this.db.sequelize.query(sql, { type: 'SELECT', transaction });
|
|
20
|
+
return results[0]['exists'];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Database from '../database';
|
|
2
|
+
import MysqlQueryInterface from './mysql-query-interface';
|
|
3
|
+
import PostgresQueryInterface from './postgres-query-interface';
|
|
4
|
+
import SqliteQueryInterface from './sqlite-query-interface';
|
|
5
|
+
|
|
6
|
+
export default function buildQueryInterface(db: Database) {
|
|
7
|
+
const map = {
|
|
8
|
+
mysql: MysqlQueryInterface,
|
|
9
|
+
postgres: PostgresQueryInterface,
|
|
10
|
+
sqlite: SqliteQueryInterface,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return new map[db.options.dialect](db);
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Database from '../database';
|
|
2
|
+
import { Collection } from '../collection';
|
|
3
|
+
import { QueryInterface as SequelizeQueryInterface, Transactionable } from 'sequelize';
|
|
4
|
+
|
|
5
|
+
export default abstract class QueryInterface {
|
|
6
|
+
sequelizeQueryInterface: SequelizeQueryInterface;
|
|
7
|
+
protected constructor(public db: Database) {
|
|
8
|
+
this.sequelizeQueryInterface = db.sequelize.getQueryInterface();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
abstract collectionTableExists(collection: Collection, options?: Transactionable): Promise<boolean>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import QueryInterface from './query-interface';
|
|
2
|
+
import { Collection } from '../collection';
|
|
3
|
+
|
|
4
|
+
export default class SqliteQueryInterface extends QueryInterface {
|
|
5
|
+
constructor(db) {
|
|
6
|
+
super(db);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async collectionTableExists(collection: Collection, options?) {
|
|
10
|
+
const transaction = options?.transaction;
|
|
11
|
+
|
|
12
|
+
const tableName = collection.model.tableName;
|
|
13
|
+
|
|
14
|
+
const sql = `SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}';`;
|
|
15
|
+
const results = await this.db.sequelize.query(sql, { type: 'SELECT', transaction });
|
|
16
|
+
return results.length > 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -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
|
});
|
|
@@ -42,6 +42,8 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
|
|
42
42
|
const getAccessor = this.accessors().get;
|
|
43
43
|
const sourceModel = await this.getSourceModel(transaction);
|
|
44
44
|
|
|
45
|
+
if (!sourceModel) return [];
|
|
46
|
+
|
|
45
47
|
if (findOptions.include && findOptions.include.length > 0) {
|
|
46
48
|
const ids = (
|
|
47
49
|
await sourceModel[getAccessor]({
|
|
@@ -103,6 +105,8 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
|
|
103
105
|
const transaction = await this.getTransaction(options);
|
|
104
106
|
|
|
105
107
|
const sourceModel = await this.getSourceModel(transaction);
|
|
108
|
+
if (!sourceModel) return 0;
|
|
109
|
+
|
|
106
110
|
const queryOptions = this.buildQueryOptions(options);
|
|
107
111
|
|
|
108
112
|
const count = await sourceModel[this.accessors().get]({
|
|
@@ -54,6 +54,8 @@ export abstract class SingleRelationRepository extends RelationRepository {
|
|
|
54
54
|
const getAccessor = this.accessors().get;
|
|
55
55
|
const sourceModel = await this.getSourceModel(transaction);
|
|
56
56
|
|
|
57
|
+
if (!sourceModel) return null;
|
|
58
|
+
|
|
57
59
|
if (findOptions?.include?.length > 0) {
|
|
58
60
|
const templateModel = await sourceModel[getAccessor]({
|
|
59
61
|
...findOptions,
|
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,7 @@ export class SyncRunner {
|
|
|
8
7
|
|
|
9
8
|
const inheritedCollection = model.collection as InheritedCollection;
|
|
10
9
|
const db = inheritedCollection.context.database;
|
|
10
|
+
|
|
11
11
|
const dialect = db.sequelize.getDialect();
|
|
12
12
|
|
|
13
13
|
const queryInterface = db.sequelize.getQueryInterface();
|
|
@@ -26,9 +26,7 @@ export class SyncRunner {
|
|
|
26
26
|
);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
const tableName = model.getTableName();
|
|
29
|
+
const tableName = inheritedCollection.addSchemaTableName();
|
|
32
30
|
|
|
33
31
|
const attributes = model.tableAttributes;
|
|
34
32
|
|
|
@@ -39,11 +37,15 @@ export class SyncRunner {
|
|
|
39
37
|
let maxSequenceVal = 0;
|
|
40
38
|
let maxSequenceName;
|
|
41
39
|
|
|
40
|
+
// find max sequence
|
|
42
41
|
if (childAttributes.id && childAttributes.id.autoIncrement) {
|
|
43
|
-
for (const parent of
|
|
42
|
+
for (const parent of parents) {
|
|
44
43
|
const sequenceNameResult = await queryInterface.sequelize.query(
|
|
45
|
-
`SELECT column_default
|
|
46
|
-
|
|
44
|
+
`SELECT column_default
|
|
45
|
+
FROM information_schema.columns
|
|
46
|
+
WHERE table_name = '${parent.model.tableName}'
|
|
47
|
+
and table_schema = '${parent.collectionSchema()}'
|
|
48
|
+
and "column_name" = 'id';`,
|
|
47
49
|
{
|
|
48
50
|
transaction,
|
|
49
51
|
},
|
|
@@ -59,13 +61,14 @@ export class SyncRunner {
|
|
|
59
61
|
throw new Error(`Can't find sequence name of ${parent}`);
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
const regex = new RegExp(/nextval\('(
|
|
64
|
+
const regex = new RegExp(/nextval\('(.*)'::regclass\)/);
|
|
63
65
|
const match = regex.exec(columnDefault);
|
|
64
66
|
|
|
65
67
|
const sequenceName = match[1];
|
|
66
68
|
|
|
67
69
|
const sequenceCurrentValResult = await queryInterface.sequelize.query(
|
|
68
|
-
`select last_value
|
|
70
|
+
`select last_value
|
|
71
|
+
from ${sequenceName}`,
|
|
69
72
|
{
|
|
70
73
|
transaction,
|
|
71
74
|
},
|
|
@@ -80,26 +83,27 @@ export class SyncRunner {
|
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
await this.createTable(tableName, childAttributes, options, model,
|
|
86
|
+
await this.createTable(tableName, childAttributes, options, model, parents);
|
|
84
87
|
|
|
88
|
+
// if we have max sequence, set it to child table
|
|
85
89
|
if (maxSequenceName) {
|
|
86
|
-
const parentsDeep = Array.from(db.inheritanceMap.getParents(inheritedCollection.name)).map(
|
|
87
|
-
|
|
90
|
+
const parentsDeep = Array.from(db.inheritanceMap.getParents(inheritedCollection.name)).map((parent) =>
|
|
91
|
+
db.getCollection(parent).addSchemaTableName(),
|
|
88
92
|
);
|
|
89
93
|
|
|
90
94
|
const sequenceTables = [...parentsDeep, tableName];
|
|
91
95
|
|
|
92
96
|
for (const sequenceTable of sequenceTables) {
|
|
93
|
-
const
|
|
97
|
+
const tableName = sequenceTable.tableName;
|
|
98
|
+
const schemaName = sequenceTable.schema;
|
|
99
|
+
|
|
100
|
+
const queryName = Boolean(tableName.match(/[A-Z]/)) && !tableName.includes(`"`) ? `"${tableName}"` : tableName;
|
|
94
101
|
|
|
95
102
|
const idColumnQuery = await queryInterface.sequelize.query(
|
|
96
|
-
`
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
AND attname = 'id'
|
|
101
|
-
AND NOT attisdropped
|
|
102
|
-
`,
|
|
103
|
+
`SELECT column_name
|
|
104
|
+
FROM information_schema.columns
|
|
105
|
+
WHERE table_name='${queryName}' and column_name='id' and table_schema = '${schemaName}';
|
|
106
|
+
`,
|
|
103
107
|
{
|
|
104
108
|
transaction,
|
|
105
109
|
},
|
|
@@ -110,7 +114,8 @@ AND NOT attisdropped
|
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
await queryInterface.sequelize.query(
|
|
113
|
-
`alter table
|
|
117
|
+
`alter table ${db.utils.quoteTable(sequenceTable)}
|
|
118
|
+
alter column id set default nextval('${maxSequenceName}')`,
|
|
114
119
|
{
|
|
115
120
|
transaction,
|
|
116
121
|
},
|
|
@@ -121,7 +126,9 @@ AND NOT attisdropped
|
|
|
121
126
|
if (options.alter) {
|
|
122
127
|
const columns = await queryInterface.describeTable(tableName, options);
|
|
123
128
|
|
|
124
|
-
for (const
|
|
129
|
+
for (const attribute in childAttributes) {
|
|
130
|
+
const columnName = childAttributes[attribute].field;
|
|
131
|
+
|
|
125
132
|
if (!columns[columnName]) {
|
|
126
133
|
await queryInterface.addColumn(tableName, columnName, childAttributes[columnName], options);
|
|
127
134
|
}
|
|
@@ -129,7 +136,7 @@ AND NOT attisdropped
|
|
|
129
136
|
}
|
|
130
137
|
}
|
|
131
138
|
|
|
132
|
-
static async createTable(tableName, attributes, options, model,
|
|
139
|
+
static async createTable(tableName, attributes, options, model, parents) {
|
|
133
140
|
let sql = '';
|
|
134
141
|
|
|
135
142
|
options = { ...options };
|
|
@@ -154,7 +161,11 @@ AND NOT attisdropped
|
|
|
154
161
|
|
|
155
162
|
sql = `${queryGenerator.createTableQuery(tableName, attributes, options)}`.replace(
|
|
156
163
|
';',
|
|
157
|
-
` INHERITS (${
|
|
164
|
+
` INHERITS (${parents
|
|
165
|
+
.map((t) => {
|
|
166
|
+
return t.addSchemaTableName();
|
|
167
|
+
})
|
|
168
|
+
.join(', ')});`,
|
|
158
169
|
);
|
|
159
170
|
|
|
160
171
|
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
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
|
+
import Database from './database';
|
|
2
3
|
import { IdentifierError } from './errors/identifier-error';
|
|
3
4
|
import { Model } from './model';
|
|
5
|
+
import lodash from 'lodash';
|
|
4
6
|
|
|
5
7
|
type HandleAppendsQueryOptions = {
|
|
6
8
|
templateModel: any;
|
|
@@ -73,3 +75,96 @@ export function checkIdentifier(value: string) {
|
|
|
73
75
|
throw new IdentifierError(`Identifier ${value} is too long`);
|
|
74
76
|
}
|
|
75
77
|
}
|
|
78
|
+
|
|
79
|
+
export function getTableName(collectionName: string, options) {
|
|
80
|
+
return options.underscored ? snakeCase(collectionName) : collectionName;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function snakeCase(name: string) {
|
|
84
|
+
return require('sequelize').Utils.underscore(name);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function patchShowConstraintsQuery(queryGenerator, db) {
|
|
88
|
+
queryGenerator.showConstraintsQuery = (tableName, constraintName) => {
|
|
89
|
+
const lines = [
|
|
90
|
+
'SELECT constraint_catalog AS "constraintCatalog",',
|
|
91
|
+
'constraint_schema AS "constraintSchema",',
|
|
92
|
+
'constraint_name AS "constraintName",',
|
|
93
|
+
'table_catalog AS "tableCatalog",',
|
|
94
|
+
'table_schema AS "tableSchema",',
|
|
95
|
+
'table_name AS "tableName",',
|
|
96
|
+
'constraint_type AS "constraintType",',
|
|
97
|
+
'is_deferrable AS "isDeferrable",',
|
|
98
|
+
'initially_deferred AS "initiallyDeferred"',
|
|
99
|
+
'from INFORMATION_SCHEMA.table_constraints',
|
|
100
|
+
`WHERE table_name='${lodash.isPlainObject(tableName) ? tableName.tableName : tableName}'`,
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
if (!constraintName) {
|
|
104
|
+
lines.push(`AND constraint_name='${constraintName}'`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (lodash.isPlainObject(tableName) && tableName.schema) {
|
|
108
|
+
lines.push(`AND table_schema='${tableName.schema}'`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return lines.join(' ');
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function patchDescribeTableQuery(queryGenerator) {
|
|
116
|
+
const describeTableQuery = function (tableName, schema) {
|
|
117
|
+
schema = schema || this.options.schema || 'public';
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
'SELECT ' +
|
|
121
|
+
'pk.constraint_type as "Constraint",' +
|
|
122
|
+
'c.column_name as "Field", ' +
|
|
123
|
+
'c.column_default as "Default",' +
|
|
124
|
+
'c.is_nullable as "Null", ' +
|
|
125
|
+
"(CASE WHEN c.udt_name = 'hstore' THEN c.udt_name ELSE c.data_type END) || (CASE WHEN c.character_maximum_length IS NOT NULL THEN '(' || c.character_maximum_length || ')' ELSE '' END) as \"Type\", " +
|
|
126
|
+
'(SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special", ' +
|
|
127
|
+
'(SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st INNER JOIN pg_catalog.pg_description pgd on (pgd.objoid=st.relid) WHERE c.ordinal_position=pgd.objsubid AND c.table_name=st.relname AND st.schemaname = c.table_schema) AS "Comment" ' +
|
|
128
|
+
'FROM information_schema.columns c ' +
|
|
129
|
+
'LEFT JOIN (SELECT tc.table_schema, tc.table_name, ' +
|
|
130
|
+
'cu.column_name, tc.constraint_type ' +
|
|
131
|
+
'FROM information_schema.TABLE_CONSTRAINTS tc ' +
|
|
132
|
+
'JOIN information_schema.KEY_COLUMN_USAGE cu ' +
|
|
133
|
+
'ON tc.table_schema=cu.table_schema and tc.table_name=cu.table_name ' +
|
|
134
|
+
'and tc.constraint_name=cu.constraint_name ' +
|
|
135
|
+
"and tc.constraint_type='PRIMARY KEY') pk " +
|
|
136
|
+
'ON pk.table_schema=c.table_schema ' +
|
|
137
|
+
'AND pk.table_name=c.table_name ' +
|
|
138
|
+
'AND pk.column_name=c.column_name ' +
|
|
139
|
+
`WHERE c.table_name = ${this.escape(tableName)} AND c.table_schema = ${this.escape(schema)}`
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
queryGenerator.describeTableQuery = describeTableQuery.bind(queryGenerator);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function patchSequelizeQueryInterface(db: Database) {
|
|
147
|
+
if (db.inDialect('postgres')) {
|
|
148
|
+
//@ts-ignore
|
|
149
|
+
const queryGenerator = db.sequelize.dialect.queryGenerator;
|
|
150
|
+
patchShowConstraintsQuery(queryGenerator, db);
|
|
151
|
+
patchDescribeTableQuery(queryGenerator);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function percent2float(value: string) {
|
|
156
|
+
if (!value.endsWith('%')) {
|
|
157
|
+
return NaN;
|
|
158
|
+
}
|
|
159
|
+
let val = value.substring(0, value.length - 1);
|
|
160
|
+
if (isNaN(+val)) {
|
|
161
|
+
return NaN;
|
|
162
|
+
}
|
|
163
|
+
const index = value.indexOf('.');
|
|
164
|
+
if (index === -1) {
|
|
165
|
+
return parseFloat(value) / 100;
|
|
166
|
+
}
|
|
167
|
+
const repeat = value.length - index - 2;
|
|
168
|
+
const v = parseInt('1' + '0'.repeat(repeat));
|
|
169
|
+
return (parseFloat(value) * v) / (100 * v);
|
|
170
|
+
}
|