@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.
Files changed (125) hide show
  1. package/lib/collection-importer.js +1 -1
  2. package/lib/collection.d.ts +9 -2
  3. package/lib/collection.js +153 -63
  4. package/lib/database-utils/index.d.ts +8 -0
  5. package/lib/database-utils/index.js +59 -0
  6. package/lib/database.d.ts +32 -3
  7. package/lib/database.js +237 -64
  8. package/lib/fields/array-field.d.ts +1 -1
  9. package/lib/fields/array-field.js +2 -2
  10. package/lib/fields/belongs-to-many-field.js +8 -3
  11. package/lib/fields/field.d.ts +1 -0
  12. package/lib/fields/field.js +37 -15
  13. package/lib/fields/has-one-field.d.ts +1 -1
  14. package/lib/fields/has-one-field.js +9 -5
  15. package/lib/fields/number-field.d.ts +9 -6
  16. package/lib/fields/number-field.js +8 -6
  17. package/lib/fields/sort-field.js +15 -1
  18. package/lib/filter-parser.js +1 -1
  19. package/lib/index.d.ts +6 -4
  20. package/lib/index.js +59 -36
  21. package/lib/mock-database.d.ts +2 -0
  22. package/lib/mock-database.js +3 -1
  23. package/lib/model.js +10 -1
  24. package/lib/options-parser.js +3 -0
  25. package/lib/query-interface/mysql-query-interface.d.ts +7 -0
  26. package/lib/query-interface/mysql-query-interface.js +39 -0
  27. package/lib/query-interface/postgres-query-interface.d.ts +6 -0
  28. package/lib/query-interface/postgres-query-interface.js +41 -0
  29. package/lib/query-interface/query-interface-builder.d.ts +2 -0
  30. package/lib/query-interface/query-interface-builder.js +23 -0
  31. package/lib/query-interface/query-interface.d.ts +9 -0
  32. package/lib/query-interface/query-interface.js +18 -0
  33. package/lib/query-interface/sqlite-query-interface.d.ts +6 -0
  34. package/lib/query-interface/sqlite-query-interface.js +38 -0
  35. package/lib/relation-repository/belongs-to-many-repository.js +4 -2
  36. package/lib/relation-repository/multiple-relation-repository.js +2 -0
  37. package/lib/relation-repository/single-relation-repository.js +1 -0
  38. package/lib/repository.js +5 -2
  39. package/lib/sync-runner.d.ts +1 -1
  40. package/lib/sync-runner.js +29 -22
  41. package/lib/types.d.ts +7 -1
  42. package/lib/update-associations.js +17 -3
  43. package/lib/update-guard.d.ts +1 -0
  44. package/lib/update-guard.js +6 -0
  45. package/lib/utils.d.ts +5 -0
  46. package/lib/utils.js +78 -0
  47. package/lib/value-parsers/array-value-parser.d.ts +8 -0
  48. package/lib/value-parsers/array-value-parser.js +76 -0
  49. package/lib/value-parsers/base-value-parser.d.ts +12 -0
  50. package/lib/value-parsers/base-value-parser.js +59 -0
  51. package/lib/value-parsers/boolean-value-parser.d.ts +4 -0
  52. package/lib/value-parsers/boolean-value-parser.js +46 -0
  53. package/lib/value-parsers/date-value-parser.d.ts +5 -0
  54. package/lib/value-parsers/date-value-parser.js +91 -0
  55. package/lib/value-parsers/index.d.ts +12 -0
  56. package/lib/value-parsers/index.js +102 -0
  57. package/lib/value-parsers/json-value-parser.d.ts +4 -0
  58. package/lib/value-parsers/json-value-parser.js +37 -0
  59. package/lib/value-parsers/number-value-parser.d.ts +4 -0
  60. package/lib/value-parsers/number-value-parser.js +49 -0
  61. package/lib/value-parsers/string-value-parser.d.ts +8 -0
  62. package/lib/value-parsers/string-value-parser.js +76 -0
  63. package/lib/value-parsers/to-many-value-parser.d.ts +13 -0
  64. package/lib/value-parsers/to-many-value-parser.js +169 -0
  65. package/lib/value-parsers/to-one-value-parser.d.ts +4 -0
  66. package/lib/value-parsers/to-one-value-parser.js +49 -0
  67. package/package.json +5 -3
  68. package/src/__tests__/bigint.test.ts +1 -1
  69. package/src/__tests__/collection-importer.test.ts +13 -1
  70. package/src/__tests__/collection.test.ts +19 -9
  71. package/src/__tests__/database.test.ts +32 -0
  72. package/src/__tests__/fields/sort-field.test.ts +23 -0
  73. package/src/__tests__/filter.test.ts +60 -0
  74. package/src/__tests__/inhertits/collection-inherits.test.ts +7 -5
  75. package/src/__tests__/percent2float.test.ts +14 -0
  76. package/src/__tests__/postgres/schema.test.ts +120 -0
  77. package/src/__tests__/underscored-options.test.ts +207 -0
  78. package/src/__tests__/update-associations-through.test.ts +73 -0
  79. package/src/__tests__/value-parsers/base.test.ts +20 -0
  80. package/src/__tests__/value-parsers/date.test.ts +67 -0
  81. package/src/__tests__/value-parsers/number.test.ts +46 -0
  82. package/src/__tests__/value-parsers/to-many.test.ts +206 -0
  83. package/src/__tests__/value-parsers/to-one.test.ts +60 -0
  84. package/src/collection-importer.ts +2 -2
  85. package/src/collection.ts +115 -17
  86. package/src/database-utils/index.ts +38 -0
  87. package/src/database.ts +188 -36
  88. package/src/fields/array-field.ts +1 -1
  89. package/src/fields/belongs-to-field.ts +1 -1
  90. package/src/fields/belongs-to-many-field.ts +8 -3
  91. package/src/fields/field.ts +48 -17
  92. package/src/fields/has-many-field.ts +1 -1
  93. package/src/fields/has-one-field.ts +11 -7
  94. package/src/fields/number-field.ts +10 -6
  95. package/src/fields/sort-field.ts +13 -1
  96. package/src/filter-parser.ts +1 -1
  97. package/src/index.ts +7 -4
  98. package/src/inherited-collection.ts +1 -0
  99. package/src/mock-database.ts +3 -1
  100. package/src/model.ts +11 -2
  101. package/src/options-parser.ts +5 -0
  102. package/src/query-interface/mysql-query-interface.ts +20 -0
  103. package/src/query-interface/postgres-query-interface.ts +22 -0
  104. package/src/query-interface/query-interface-builder.ts +14 -0
  105. package/src/query-interface/query-interface.ts +12 -0
  106. package/src/query-interface/sqlite-query-interface.ts +18 -0
  107. package/src/relation-repository/belongs-to-many-repository.ts +4 -2
  108. package/src/relation-repository/multiple-relation-repository.ts +4 -0
  109. package/src/relation-repository/single-relation-repository.ts +2 -0
  110. package/src/repository.ts +8 -3
  111. package/src/sync-runner.ts +35 -24
  112. package/src/types.ts +12 -1
  113. package/src/update-associations.ts +12 -5
  114. package/src/update-guard.ts +6 -0
  115. package/src/utils.ts +95 -0
  116. package/src/value-parsers/array-value-parser.ts +30 -0
  117. package/src/value-parsers/base-value-parser.ts +40 -0
  118. package/src/value-parsers/boolean-value-parser.ts +29 -0
  119. package/src/value-parsers/date-value-parser.ts +38 -0
  120. package/src/value-parsers/index.ts +46 -0
  121. package/src/value-parsers/json-value-parser.ts +19 -0
  122. package/src/value-parsers/number-value-parser.ts +29 -0
  123. package/src/value-parsers/string-value-parser.ts +31 -0
  124. package/src/value-parsers/to-many-value-parser.ts +85 -0
  125. package/src/value-parsers/to-one-value-parser.ts +20 -0
@@ -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 './inherited-collection';
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 * from './collection-importer';
19
- export * from './filter-match';
20
- export * from './field-repository/array-field-repository';
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');
@@ -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 { DataTypes, Model as SequelizeModel, ModelStatic } from 'sequelize';
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')) {
@@ -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, { ...options, action: 'create' });
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
 
@@ -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 parentTables = parents.map((parent) => parent.model.tableName);
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 parentTables) {
42
+ for (const parent of parents) {
44
43
  const sequenceNameResult = await queryInterface.sequelize.query(
45
- `SELECT column_default FROM information_schema.columns WHERE
46
- table_name='${parent}' and "column_name" = 'id';`,
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\('("?\w+"?)\'.*\)/);
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 from ${sequenceName}`,
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, parentTables);
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
- (parent) => db.getCollection(parent).model.tableName,
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 queryName = Boolean(sequenceTable.match(/[A-Z]/)) ? `"${sequenceTable}"` : sequenceTable;
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
- SELECT true
98
- FROM pg_attribute
99
- WHERE attrelid = '${queryName}'::regclass -- cast to a registered class (table)
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 "${sequenceTable}" alter column id set default nextval('${maxSequenceName}')`,
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 columnName in childAttributes) {
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, parentTables) {
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 (${parentTables.map((t) => `"${t}"`).join(', ')});`,
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
- await model[addAccessor](item[pk], accessorOptions);
465
+ if (!created.includes(item[pk])) {
466
+ await model[addAccessor](item[pk], accessorOptions);
467
+ }
461
468
  if (!recursive) {
462
469
  continue;
463
470
  }
@@ -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
+ }