@nocobase/database 0.9.1-alpha.1 → 0.9.2-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.
Files changed (173) hide show
  1. package/lib/collection-group-manager.d.ts +13 -0
  2. package/lib/collection-group-manager.js +91 -0
  3. package/lib/collection-importer.js +0 -24
  4. package/lib/collection.d.ts +26 -4
  5. package/lib/collection.js +190 -234
  6. package/lib/database-utils/index.js +3 -15
  7. package/lib/database.d.ts +9 -0
  8. package/lib/database.js +171 -299
  9. package/lib/decorators/must-have-filter-decorator.js +0 -7
  10. package/lib/decorators/transaction-decorator.js +5 -18
  11. package/lib/errors/identifier-error.js +0 -3
  12. package/lib/features/ReferencesMap.js +1 -14
  13. package/lib/features/referential-integrity-check.js +7 -21
  14. package/lib/field-repository/array-field-repository.js +5 -45
  15. package/lib/fields/array-field.js +0 -13
  16. package/lib/fields/belongs-to-field.js +24 -50
  17. package/lib/fields/belongs-to-many-field.js +29 -47
  18. package/lib/fields/boolean-field.js +0 -7
  19. package/lib/fields/context-field.js +2 -23
  20. package/lib/fields/date-field.d.ts +4 -0
  21. package/lib/fields/date-field.js +15 -7
  22. package/lib/fields/field.js +32 -85
  23. package/lib/fields/has-many-field.js +16 -49
  24. package/lib/fields/has-one-field.js +18 -52
  25. package/lib/fields/index.js +0 -44
  26. package/lib/fields/json-field.js +0 -12
  27. package/lib/fields/number-field.js +0 -23
  28. package/lib/fields/password-field.js +8 -35
  29. package/lib/fields/radio-field.js +0 -18
  30. package/lib/fields/relation-field.js +4 -16
  31. package/lib/fields/set-field.js +0 -8
  32. package/lib/fields/sort-field.js +84 -73
  33. package/lib/fields/string-field.js +0 -7
  34. package/lib/fields/text-field.js +0 -7
  35. package/lib/fields/time-field.js +0 -7
  36. package/lib/fields/uid-field.js +4 -22
  37. package/lib/fields/uuid-field.js +3 -12
  38. package/lib/fields/virtual-field.js +0 -7
  39. package/lib/filter-match.js +7 -22
  40. package/lib/filter-parser.js +38 -102
  41. package/lib/index.d.ts +3 -0
  42. package/lib/index.js +36 -42
  43. package/lib/inherited-collection.js +15 -62
  44. package/lib/inherited-map.js +7 -48
  45. package/lib/listeners/adjacency-list.d.ts +3 -0
  46. package/lib/listeners/adjacency-list.js +91 -0
  47. package/lib/listeners/index.d.ts +2 -0
  48. package/lib/listeners/index.js +12 -0
  49. package/lib/magic-attribute-model.js +58 -114
  50. package/lib/migration.js +7 -28
  51. package/lib/mock-database.d.ts +4 -4
  52. package/lib/mock-database.js +15 -18
  53. package/lib/model-hook.js +4 -35
  54. package/lib/model.js +12 -54
  55. package/lib/operators/array.js +2 -32
  56. package/lib/operators/association.js +0 -6
  57. package/lib/operators/boolean.js +0 -6
  58. package/lib/operators/child-collection.d.ts +2 -0
  59. package/lib/operators/child-collection.js +32 -0
  60. package/lib/operators/date.js +123 -60
  61. package/lib/operators/empty.js +3 -32
  62. package/lib/operators/eq.d.ts +2 -0
  63. package/lib/operators/eq.js +26 -0
  64. package/lib/operators/index.js +4 -7
  65. package/lib/operators/ne.js +5 -5
  66. package/lib/operators/notIn.js +0 -5
  67. package/lib/operators/string.js +0 -11
  68. package/lib/operators/utils.js +0 -6
  69. package/lib/options-parser.d.ts +1 -1
  70. package/lib/options-parser.js +47 -107
  71. package/lib/playground.js +0 -4
  72. package/lib/query-interface/mysql-query-interface.d.ts +18 -0
  73. package/lib/query-interface/mysql-query-interface.js +88 -0
  74. package/lib/query-interface/postgres-query-interface.d.ts +14 -0
  75. package/lib/query-interface/postgres-query-interface.js +99 -0
  76. package/lib/query-interface/query-interface-builder.d.ts +2 -0
  77. package/lib/query-interface/query-interface-builder.js +18 -0
  78. package/lib/query-interface/query-interface.d.ts +21 -0
  79. package/lib/query-interface/query-interface.js +48 -0
  80. package/lib/query-interface/sqlite-query-interface.d.ts +17 -0
  81. package/lib/query-interface/sqlite-query-interface.js +89 -0
  82. package/lib/relation-repository/belongs-to-many-repository.js +21 -78
  83. package/lib/relation-repository/belongs-to-repository.js +0 -3
  84. package/lib/relation-repository/hasmany-repository.js +8 -44
  85. package/lib/relation-repository/hasone-repository.js +0 -3
  86. package/lib/relation-repository/multiple-relation-repository.js +16 -68
  87. package/lib/relation-repository/relation-repository.js +5 -42
  88. package/lib/relation-repository/single-relation-repository.js +6 -43
  89. package/lib/repository.d.ts +1 -0
  90. package/lib/repository.js +36 -182
  91. package/lib/sql-parser/index.js +10527 -0
  92. package/lib/sql-parser/sql.pegjs +1297 -0
  93. package/lib/sync-runner.d.ts +1 -1
  94. package/lib/sync-runner.js +26 -64
  95. package/lib/update-associations.js +58 -157
  96. package/lib/update-guard.js +10 -49
  97. package/lib/utils.js +16 -54
  98. package/lib/value-parsers/array-value-parser.js +3 -21
  99. package/lib/value-parsers/base-value-parser.js +0 -13
  100. package/lib/value-parsers/boolean-value-parser.js +4 -10
  101. package/lib/value-parsers/date-value-parser.js +0 -23
  102. package/lib/value-parsers/index.js +0 -10
  103. package/lib/value-parsers/json-value-parser.js +0 -7
  104. package/lib/value-parsers/number-value-parser.js +0 -9
  105. package/lib/value-parsers/string-value-parser.js +3 -20
  106. package/lib/value-parsers/to-many-value-parser.js +1 -42
  107. package/lib/value-parsers/to-one-value-parser.js +0 -14
  108. package/lib/view/field-type-map.d.ts +47 -0
  109. package/lib/view/field-type-map.js +56 -0
  110. package/lib/view/view-inference.d.ts +31 -0
  111. package/lib/view/view-inference.js +92 -0
  112. package/lib/view-collection.d.ts +6 -0
  113. package/lib/view-collection.js +24 -0
  114. package/package.json +4 -3
  115. package/src/__tests__/collection.test.ts +44 -0
  116. package/src/__tests__/fields/date.test.ts +75 -0
  117. package/src/__tests__/fields/sort-field.test.ts +100 -0
  118. package/src/__tests__/filter.test.ts +60 -0
  119. package/src/__tests__/group.test.ts +50 -0
  120. package/src/__tests__/inhertits/collection-inherits.test.ts +114 -0
  121. package/src/__tests__/operator/date-operator.test.ts +244 -98
  122. package/src/__tests__/operator/eq.test.ts +76 -0
  123. package/src/__tests__/operator/ne.test.ts +19 -1
  124. package/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +82 -0
  125. package/src/__tests__/repository/find.test.ts +33 -0
  126. package/src/__tests__/repository.test.ts +88 -0
  127. package/src/__tests__/sql-parser.test.ts +13 -0
  128. package/src/__tests__/tree.test.ts +217 -0
  129. package/src/__tests__/view/list-view.test.ts +34 -0
  130. package/src/__tests__/view/view-collection.test.ts +199 -0
  131. package/src/__tests__/view/view-inference.test.ts +145 -0
  132. package/src/__tests__/view/view-repository.test.ts +67 -0
  133. package/src/collection-group-manager.ts +94 -0
  134. package/src/collection.ts +126 -16
  135. package/src/database-utils/index.ts +1 -0
  136. package/src/database.ts +98 -17
  137. package/src/features/ReferencesMap.ts +3 -2
  138. package/src/fields/belongs-to-many-field.ts +23 -4
  139. package/src/fields/date-field.ts +18 -0
  140. package/src/fields/field.ts +17 -7
  141. package/src/fields/json-field.ts +1 -0
  142. package/src/fields/sort-field.ts +90 -29
  143. package/src/filter-parser.ts +2 -1
  144. package/src/index.ts +3 -1
  145. package/src/listeners/adjacency-list.ts +60 -0
  146. package/src/listeners/index.ts +7 -0
  147. package/src/mock-database.ts +14 -2
  148. package/src/model.ts +4 -0
  149. package/src/operators/child-collection.ts +24 -0
  150. package/src/operators/date.ts +108 -24
  151. package/src/operators/eq.ts +14 -0
  152. package/src/operators/index.ts +2 -0
  153. package/src/operators/ne.ts +12 -7
  154. package/src/options-parser.ts +25 -11
  155. package/src/query-interface/mysql-query-interface.ts +72 -0
  156. package/src/query-interface/postgres-query-interface.ts +103 -0
  157. package/src/query-interface/query-interface-builder.ts +14 -0
  158. package/src/query-interface/query-interface.ts +43 -0
  159. package/src/query-interface/sqlite-query-interface.ts +79 -0
  160. package/src/relation-repository/belongs-to-many-repository.ts +20 -1
  161. package/src/relation-repository/hasmany-repository.ts +5 -3
  162. package/src/relation-repository/multiple-relation-repository.ts +13 -1
  163. package/src/relation-repository/single-relation-repository.ts +2 -0
  164. package/src/repository.ts +6 -13
  165. package/src/sql-parser/index.js +10698 -0
  166. package/src/sql-parser/readme.md +2 -0
  167. package/src/sql-parser/sql.pegjs +1297 -0
  168. package/src/sync-runner.ts +27 -32
  169. package/src/update-associations.ts +26 -22
  170. package/src/utils.ts +4 -3
  171. package/src/view/field-type-map.ts +56 -0
  172. package/src/view/view-inference.ts +106 -0
  173. package/src/view-collection.ts +21 -0
@@ -181,6 +181,80 @@ describe('repository.find', () => {
181
181
  });
182
182
  });
183
183
 
184
+ describe('repository create with belongs to many', () => {
185
+ let db: Database;
186
+
187
+ beforeEach(async () => {
188
+ db = mockDatabase({
189
+ tablePrefix: '',
190
+ });
191
+ await db.clean({ drop: true });
192
+ });
193
+
194
+ afterEach(async () => [await db.close()]);
195
+
196
+ it('should save value at through table', async () => {
197
+ const Product = db.collection({
198
+ name: 'products',
199
+ fields: [
200
+ { type: 'string', name: 'name' },
201
+ { type: 'integer', name: 'price' },
202
+ ],
203
+ });
204
+
205
+ const OrderProduct = db.collection({
206
+ name: 'orders_products',
207
+ fields: [{ type: 'integer', name: 'quantity' }],
208
+ });
209
+
210
+ const Order = db.collection({
211
+ name: 'orders',
212
+ fields: [
213
+ {
214
+ type: 'belongsToMany',
215
+ name: 'products',
216
+ through: 'orders_products',
217
+ },
218
+ ],
219
+ });
220
+
221
+ await db.sync();
222
+
223
+ await Product.repository.create({
224
+ values: [
225
+ {
226
+ name: 'product1',
227
+ price: 100,
228
+ },
229
+ {
230
+ name: 'product2',
231
+ price: 200,
232
+ },
233
+ ],
234
+ });
235
+
236
+ const p1 = await Product.repository.findOne({
237
+ filter: { name: 'product1' },
238
+ });
239
+
240
+ await Order.repository.create({
241
+ values: {
242
+ products: [
243
+ {
244
+ id: p1.get('id'),
245
+ orders_products: {
246
+ quantity: 20,
247
+ },
248
+ },
249
+ ],
250
+ },
251
+ });
252
+
253
+ const through = await OrderProduct.repository.findOne();
254
+ expect(through.get('quantity')).toBe(20);
255
+ });
256
+ });
257
+
184
258
  describe('repository.create', () => {
185
259
  let db: Database;
186
260
  let User: Collection;
@@ -311,6 +385,20 @@ describe('repository.update', () => {
311
385
  name: 'post1',
312
386
  userId: user.id,
313
387
  });
388
+
389
+ await User.repository.update({
390
+ filterByTk: user.id,
391
+ values: {
392
+ posts: [{ name: 'post2' }, { name: 'post3' }],
393
+ },
394
+ });
395
+
396
+ const updated2 = await User.repository.findOne({
397
+ filterByTk: user.id,
398
+ appends: ['posts'],
399
+ });
400
+
401
+ expect(updated2.posts.length).toBe(2);
314
402
  });
315
403
 
316
404
  it('update2', async () => {
@@ -0,0 +1,13 @@
1
+ import sqlParser from '../sql-parser';
2
+
3
+ describe('sql parser', () => {
4
+ it('should parse sql', function () {
5
+ const sql = `select users.id as id from users`;
6
+ const { ast } = sqlParser.parse(sql);
7
+ const columns = ast.columns;
8
+ const firstColumn = columns[0];
9
+
10
+ expect(firstColumn['expr']['table']).toEqual('users');
11
+ expect(firstColumn['expr']['column']).toEqual('id');
12
+ });
13
+ });
@@ -0,0 +1,217 @@
1
+ import { Database } from '../database';
2
+ import { mockDatabase } from './';
3
+
4
+ describe('sort', function () {
5
+ let db: Database;
6
+
7
+ beforeEach(async () => {
8
+ db = mockDatabase();
9
+ });
10
+
11
+ afterEach(async () => {
12
+ await db.close();
13
+ });
14
+
15
+ it('should be auto completed', () => {
16
+ const collection = db.collection({
17
+ name: 'categories',
18
+ tree: 'adjacency-list',
19
+ fields: [
20
+ {
21
+ type: 'belongsTo',
22
+ name: 'parent',
23
+ treeParent: true,
24
+ },
25
+ {
26
+ type: 'hasMany',
27
+ name: 'children',
28
+ treeChildren: true,
29
+ },
30
+ ],
31
+ });
32
+ expect(collection.treeChildrenField?.name).toBe('children');
33
+ expect(collection.treeParentField?.name).toBe('parent');
34
+ expect(collection.getField('parent').options.target).toBe('categories');
35
+ expect(collection.getField('parent').options.foreignKey).toBe('parentId');
36
+ expect(collection.getField('children').options.target).toBe('categories');
37
+ expect(collection.getField('children').options.foreignKey).toBe('parentId');
38
+ });
39
+
40
+ it('should be auto completed', () => {
41
+ const collection = db.collection({
42
+ name: 'categories',
43
+ tree: 'adjacency-list',
44
+ fields: [
45
+ {
46
+ type: 'belongsTo',
47
+ name: 'parent',
48
+ foreignKey: 'cid',
49
+ treeParent: true,
50
+ },
51
+ {
52
+ type: 'hasMany',
53
+ name: 'children',
54
+ foreignKey: 'cid',
55
+ treeChildren: true,
56
+ },
57
+ ],
58
+ });
59
+ expect(collection.treeChildrenField?.name).toBe('children');
60
+ expect(collection.treeParentField?.name).toBe('parent');
61
+ expect(collection.getField('parent').options.target).toBe('categories');
62
+ expect(collection.getField('parent').options.foreignKey).toBe('cid');
63
+ expect(collection.getField('children').options.target).toBe('categories');
64
+ expect(collection.getField('children').options.foreignKey).toBe('cid');
65
+ });
66
+
67
+ const values = [
68
+ {
69
+ name: '1',
70
+ __index: '0',
71
+ children: [
72
+ {
73
+ name: '1-1',
74
+ __index: '0.children.0',
75
+ children: [
76
+ {
77
+ name: '1-1-1',
78
+ __index: '0.children.0.children.0',
79
+ children: [
80
+ {
81
+ name: '1-1-1-1',
82
+ __index: '0.children.0.children.0.children.0',
83
+ },
84
+ ],
85
+ },
86
+ ],
87
+ },
88
+ ],
89
+ },
90
+ {
91
+ name: '2',
92
+ __index: '1',
93
+ children: [
94
+ {
95
+ name: '2-1',
96
+ __index: '1.children.0',
97
+ children: [
98
+ {
99
+ name: '2-1-1',
100
+ __index: '1.children.0.children.0',
101
+ children: [
102
+ {
103
+ name: '2-1-1-1',
104
+ __index: '1.children.0.children.0.children.0',
105
+ },
106
+ ],
107
+ },
108
+ ],
109
+ },
110
+ ],
111
+ },
112
+ ];
113
+
114
+ it('should be tree', async () => {
115
+ const collection = db.collection({
116
+ name: 'categories',
117
+ tree: 'adjacency-list',
118
+ fields: [
119
+ {
120
+ type: 'string',
121
+ name: 'name',
122
+ },
123
+ {
124
+ type: 'string',
125
+ name: 'description',
126
+ },
127
+ {
128
+ type: 'belongsTo',
129
+ name: 'parent',
130
+ treeParent: true,
131
+ },
132
+ {
133
+ type: 'hasMany',
134
+ name: 'children',
135
+ treeChildren: true,
136
+ },
137
+ ],
138
+ });
139
+ await db.sync();
140
+
141
+ await db.getRepository('categories').create({
142
+ values,
143
+ });
144
+
145
+ const instances = await db.getRepository('categories').find({
146
+ filter: {
147
+ parentId: null,
148
+ },
149
+ tree: true,
150
+ fields: ['id', 'name'],
151
+ sort: 'id',
152
+ });
153
+
154
+ expect(instances.map((i) => i.toJSON())).toMatchObject(values);
155
+
156
+ const instance = await db.getRepository('categories').findOne({
157
+ filterByTk: 1,
158
+ tree: true,
159
+ fields: ['id', 'name'],
160
+ });
161
+
162
+ expect(instance.toJSON()).toMatchObject(values[0]);
163
+ });
164
+
165
+ it('should be tree', async () => {
166
+ const collection = db.collection({
167
+ name: 'categories',
168
+ tree: 'adjacency-list',
169
+ fields: [
170
+ {
171
+ type: 'string',
172
+ name: 'name',
173
+ },
174
+ {
175
+ type: 'string',
176
+ name: 'description',
177
+ },
178
+ {
179
+ type: 'belongsTo',
180
+ name: 'parent',
181
+ foreignKey: 'cid',
182
+ treeParent: true,
183
+ },
184
+ {
185
+ type: 'hasMany',
186
+ name: 'children',
187
+ foreignKey: 'cid',
188
+ treeChildren: true,
189
+ },
190
+ ],
191
+ });
192
+ await db.sync();
193
+
194
+ await db.getRepository('categories').create({
195
+ values,
196
+ });
197
+
198
+ const instances = await db.getRepository('categories').find({
199
+ filter: {
200
+ cid: null,
201
+ },
202
+ tree: true,
203
+ fields: ['id', 'name'],
204
+ sort: 'id',
205
+ });
206
+
207
+ expect(instances.map((i) => i.toJSON())).toMatchObject(values);
208
+
209
+ const instance = await db.getRepository('categories').findOne({
210
+ filterByTk: 1,
211
+ tree: true,
212
+ fields: ['id', 'name'],
213
+ });
214
+
215
+ expect(instance.toJSON()).toMatchObject(values[0]);
216
+ });
217
+ });
@@ -0,0 +1,34 @@
1
+ import { Database, mockDatabase } from '@nocobase/database';
2
+
3
+ describe('list view', () => {
4
+ let db: Database;
5
+
6
+ beforeEach(async () => {
7
+ db = mockDatabase({
8
+ tablePrefix: '',
9
+ });
10
+ await db.clean({ drop: true });
11
+ });
12
+
13
+ afterEach(async () => {
14
+ await db.close();
15
+ });
16
+
17
+ it('should list view', async () => {
18
+ const dropViewSQL1 = `DROP VIEW IF EXISTS test1`;
19
+ await db.sequelize.query(dropViewSQL1);
20
+
21
+ const dropViewSQL2 = `DROP VIEW IF EXISTS test2`;
22
+ await db.sequelize.query(dropViewSQL2);
23
+
24
+ const sql1 = `CREATE VIEW test1 AS SELECT 1`;
25
+ const sql2 = `CREATE VIEW test2 AS SELECT 2`;
26
+
27
+ await db.sequelize.query(sql1);
28
+ await db.sequelize.query(sql2);
29
+
30
+ const results = await db.queryInterface.listViews();
31
+ expect(results.find((item) => item.name === 'test1')).toBeTruthy();
32
+ expect(results.find((item) => item.name === 'test2')).toBeTruthy();
33
+ });
34
+ });
@@ -0,0 +1,199 @@
1
+ import { Database, mockDatabase } from '@nocobase/database';
2
+ import { uid } from '@nocobase/utils';
3
+ import { ViewCollection } from '../../view-collection';
4
+
5
+ describe('create view', () => {
6
+ let db: Database;
7
+
8
+ beforeEach(async () => {
9
+ db = mockDatabase({
10
+ tablePrefix: '',
11
+ });
12
+ await db.clean({ drop: true });
13
+ });
14
+
15
+ afterEach(async () => {
16
+ await db.close();
17
+ });
18
+
19
+ it('should create view collection in difference schema', async () => {
20
+ if (!db.inDialect('postgres')) return;
21
+ const schemaName = `t_${uid(6)}`;
22
+ const testSchemaSql = `CREATE SCHEMA IF NOT EXISTS ${schemaName};`;
23
+ await db.sequelize.query(testSchemaSql);
24
+
25
+ const viewName = 'test_view';
26
+
27
+ const viewSQL = `CREATE OR REPLACE VIEW ${schemaName}.test_view AS SELECT 1+1 as result`;
28
+ await db.sequelize.query(viewSQL);
29
+
30
+ const viewCollection = db.collection({
31
+ name: viewName,
32
+ schema: schemaName,
33
+ view: true,
34
+ fields: [
35
+ {
36
+ type: 'string',
37
+ name: 'result',
38
+ },
39
+ ],
40
+ });
41
+
42
+ const results = await viewCollection.repository.find();
43
+
44
+ expect(results.length).toBe(1);
45
+ });
46
+
47
+ it('should create view collection', async () => {
48
+ const UserCollection = db.collection({
49
+ name: 'users',
50
+ fields: [
51
+ {
52
+ type: 'string',
53
+ name: 'name',
54
+ },
55
+ {
56
+ type: 'hasOne',
57
+ name: 'profile',
58
+ foreignKey: 'user_id',
59
+ },
60
+ ],
61
+ });
62
+
63
+ const ProfileCollection = db.collection({
64
+ name: 'profiles',
65
+ fields: [
66
+ {
67
+ type: 'integer',
68
+ name: 'age',
69
+ },
70
+ {
71
+ type: 'belongsTo',
72
+ name: 'user',
73
+ foreignKey: 'user_id',
74
+ },
75
+ ],
76
+ });
77
+
78
+ await db.sync();
79
+
80
+ await UserCollection.repository.create({
81
+ values: {
82
+ name: 'foo',
83
+ profile: {
84
+ age: 18,
85
+ },
86
+ },
87
+ });
88
+ const schema = UserCollection.collectionSchema();
89
+ const viewName = 'users_with_profile';
90
+
91
+ const appendSchema = db.inDialect('postgres') ? `"${schema}".` : '';
92
+
93
+ const dropViewSQL = `DROP VIEW IF EXISTS ${appendSchema}${viewName}`;
94
+ await db.sequelize.query(dropViewSQL);
95
+ const viewSql = `CREATE VIEW ${appendSchema}${viewName} AS SELECT users.name, profiles.age FROM ${appendSchema}${UserCollection.model.tableName} as users LEFT JOIN ${appendSchema}${ProfileCollection.model.tableName} as profiles ON users.id = profiles.user_id;`;
96
+
97
+ await db.sequelize.query(viewSql);
98
+
99
+ db.collection({
100
+ name: viewName,
101
+ view: true,
102
+ fields: [
103
+ {
104
+ type: 'string',
105
+ name: 'name',
106
+ },
107
+ {
108
+ type: 'integer',
109
+ name: 'age',
110
+ },
111
+ ],
112
+ });
113
+ const UserWithProfileView = db.getCollection(viewName);
114
+ expect(UserWithProfileView).toBeInstanceOf(ViewCollection);
115
+
116
+ const fooData = await UserWithProfileView.repository.findOne({
117
+ filter: {
118
+ name: 'foo',
119
+ },
120
+ });
121
+
122
+ console.log(fooData);
123
+ expect(fooData.get('name')).toBe('foo');
124
+ expect(fooData.get('age')).toBe(18);
125
+ });
126
+
127
+ it('should not sync view collection', async () => {
128
+ const dropViewSQL = `DROP VIEW IF EXISTS test_view`;
129
+ await db.sequelize.query(dropViewSQL);
130
+
131
+ const viewSql = `CREATE VIEW test_view AS SELECT 1+1 as result`;
132
+
133
+ await db.sequelize.query(viewSql);
134
+ const viewCollection = db.collection({
135
+ name: 'view_collection',
136
+ viewName: 'test_view',
137
+ fields: [
138
+ {
139
+ type: 'string',
140
+ name: 'result',
141
+ },
142
+ ],
143
+ });
144
+
145
+ const jestFn = jest.fn();
146
+
147
+ db.on('beforeSync', jestFn);
148
+
149
+ await viewCollection.sync();
150
+ expect(jestFn).not.toBeCalled();
151
+ });
152
+
153
+ it('should create view collection with source field options', async () => {
154
+ const UserCollection = db.collection({
155
+ name: 'users',
156
+ fields: [
157
+ {
158
+ name: 'name',
159
+ type: 'string',
160
+ patterns: [
161
+ {
162
+ type: 'integer',
163
+ options: { key: 1 },
164
+ },
165
+ ],
166
+ },
167
+ ],
168
+ });
169
+
170
+ await db.sync();
171
+
172
+ const viewName = 'users_view';
173
+
174
+ const dropViewSQL = `DROP VIEW IF EXISTS ${viewName}`;
175
+ await db.sequelize.query(dropViewSQL);
176
+
177
+ const viewSQL = `
178
+ CREATE VIEW ${viewName} as SELECT users.* FROM ${UserCollection.quotedTableName()} as users
179
+ `;
180
+
181
+ await db.sequelize.query(viewSQL);
182
+
183
+ // create view collection
184
+ const ViewCollection = db.collection({
185
+ name: viewName,
186
+ view: true,
187
+ fields: [
188
+ {
189
+ name: 'name',
190
+ type: 'string',
191
+ source: 'users.name',
192
+ },
193
+ ],
194
+ });
195
+
196
+ const viewNameField = ViewCollection.getField('name');
197
+ expect(viewNameField.options.patterns).toEqual(UserCollection.getField('name').options.patterns);
198
+ });
199
+ });
@@ -0,0 +1,145 @@
1
+ import { Database, mockDatabase } from '@nocobase/database';
2
+ import { ViewFieldInference } from '../../view/view-inference';
3
+
4
+ describe('view inference', function () {
5
+ let db: Database;
6
+
7
+ beforeEach(async () => {
8
+ db = mockDatabase({
9
+ tablePrefix: '',
10
+ });
11
+ await db.clean({ drop: true });
12
+ });
13
+
14
+ afterEach(async () => {
15
+ await db.close();
16
+ });
17
+
18
+ it('should infer field with alias', async () => {
19
+ if (db.options.dialect !== 'postgres') return;
20
+
21
+ const UserCollection = db.collection({
22
+ name: 'users',
23
+ fields: [
24
+ {
25
+ name: 'id',
26
+ type: 'bigInt',
27
+ interface: 'bigInt',
28
+ },
29
+ {
30
+ name: 'name',
31
+ type: 'string',
32
+ interface: 'test',
33
+ },
34
+ ],
35
+ });
36
+
37
+ await db.sync();
38
+
39
+ const viewName = 'user_posts';
40
+
41
+ const dropViewSQL = `DROP VIEW IF EXISTS ${viewName}`;
42
+ await db.sequelize.query(dropViewSQL);
43
+
44
+ const viewSQL = `
45
+ CREATE VIEW ${viewName} as SELECT 1 as const_field, users.id as user_id_field, users.name FROM ${UserCollection.quotedTableName()} as users
46
+ `;
47
+
48
+ await db.sequelize.query(viewSQL);
49
+
50
+ const inferredFields = await ViewFieldInference.inferFields({
51
+ db,
52
+ viewName,
53
+ viewSchema: 'public',
54
+ });
55
+
56
+ expect(inferredFields['user_id_field'].source).toBe('users.id');
57
+ expect(inferredFields['name'].source).toBe('users.name');
58
+ });
59
+
60
+ it('should infer collection fields', async () => {
61
+ const UserCollection = db.collection({
62
+ name: 'users',
63
+ fields: [
64
+ {
65
+ name: 'name',
66
+ type: 'string',
67
+ interface: 'test',
68
+ },
69
+ {
70
+ name: 'age',
71
+ type: 'integer',
72
+ interface: 'test',
73
+ },
74
+ {
75
+ name: 'profile',
76
+ type: 'json',
77
+ interface: 'test',
78
+ },
79
+ {
80
+ name: 'posts',
81
+ type: 'hasMany',
82
+ interface: 'test',
83
+ },
84
+ ],
85
+ });
86
+
87
+ const PostCollection = db.collection({
88
+ name: 'posts',
89
+ fields: [
90
+ {
91
+ name: 'title',
92
+ type: 'string',
93
+ interface: 'test',
94
+ },
95
+ {
96
+ name: 'user',
97
+ type: 'belongsTo',
98
+ interface: 'test',
99
+ },
100
+ ],
101
+ });
102
+
103
+ await db.sync();
104
+
105
+ const viewName = 'user_posts';
106
+
107
+ const dropViewSQL = `DROP VIEW IF EXISTS ${viewName}`;
108
+ await db.sequelize.query(dropViewSQL);
109
+
110
+ const viewSQL = `
111
+ CREATE VIEW ${viewName} as SELECT 1 as const_field, users.* FROM ${UserCollection.quotedTableName()} as users
112
+ `;
113
+
114
+ await db.sequelize.query(viewSQL);
115
+
116
+ const inferredFields = await ViewFieldInference.inferFields({
117
+ db,
118
+ viewName,
119
+ viewSchema: 'public',
120
+ });
121
+
122
+ const createdAt = UserCollection.model.rawAttributes['createdAt'].field;
123
+ expect(inferredFields[createdAt]['type']).toBe('date');
124
+
125
+ if (db.options.dialect == 'sqlite') {
126
+ expect(inferredFields['name']).toMatchObject({
127
+ name: 'name',
128
+ type: 'string',
129
+ });
130
+ } else {
131
+ expect(inferredFields['name']).toMatchObject({
132
+ name: 'name',
133
+ type: 'string',
134
+ source: 'users.name',
135
+ });
136
+
137
+ expect(inferredFields['const_field']).toMatchObject({
138
+ name: 'const_field',
139
+ type: 'integer',
140
+ });
141
+ }
142
+
143
+ await db.sequelize.query(dropViewSQL);
144
+ });
145
+ });