@nocobase/database 0.10.0-alpha.4 → 0.10.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,7 +31,7 @@ class UidField extends _field.Field {
31
31
  _this$options$prefix = _this$options.prefix,
32
32
  prefix = _this$options$prefix === void 0 ? '' : _this$options$prefix,
33
33
  pattern = _this$options.pattern;
34
- const re = new RegExp(pattern || '^[A-Za-z0-9][A-Za-z0-9_-]*$');
34
+ const re = new RegExp(pattern || '^[A-Za-z0-9_][A-Za-z0-9_-]*$');
35
35
  this.listener = /*#__PURE__*/function () {
36
36
  var _ref = _asyncToGenerator(function* (instance) {
37
37
  const value = instance.get(name);
package/lib/index.d.ts CHANGED
@@ -23,3 +23,4 @@ export * from './value-parsers';
23
23
  export * from './collection-group-manager';
24
24
  export * from './view-collection';
25
25
  export * from './view/view-inference';
26
+ export { default as FilterParser } from './filter-parser';
package/lib/index.js CHANGED
@@ -6,7 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  var _exportNames = {
7
7
  DataTypes: true,
8
8
  Op: true,
9
- snakeCase: true
9
+ snakeCase: true,
10
+ FilterParser: true
10
11
  };
11
12
  Object.defineProperty(exports, "DataTypes", {
12
13
  enumerable: true,
@@ -14,6 +15,12 @@ Object.defineProperty(exports, "DataTypes", {
14
15
  return _sequelize().DataTypes;
15
16
  }
16
17
  });
18
+ Object.defineProperty(exports, "FilterParser", {
19
+ enumerable: true,
20
+ get: function get() {
21
+ return _filterParser.default;
22
+ }
23
+ });
17
24
  Object.defineProperty(exports, "Op", {
18
25
  enumerable: true,
19
26
  get: function get() {
@@ -303,4 +310,6 @@ Object.keys(_viewInference).forEach(function (key) {
303
310
  return _viewInference[key];
304
311
  }
305
312
  });
306
- });
313
+ });
314
+ var _filterParser = _interopRequireDefault(require("./filter-parser"));
315
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -4,6 +4,13 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
+ function _lodash() {
8
+ const data = _interopRequireDefault(require("lodash"));
9
+ _lodash = function _lodash() {
10
+ return data;
11
+ };
12
+ return data;
13
+ }
7
14
  function _sequelize() {
8
15
  const data = require("sequelize");
9
16
  _sequelize = function _sequelize() {
@@ -11,22 +18,37 @@ function _sequelize() {
11
18
  };
12
19
  return data;
13
20
  }
21
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14
22
  const mapVal = (values, db) => values.map(v => {
15
23
  const collection = db.getCollection(v);
16
24
  return _sequelize().Sequelize.literal(`'${collection.tableNameAsString()}'::regclass`);
17
25
  });
26
+ const filterItems = (values, db) => {
27
+ return _lodash().default.castArray(values).map(v => {
28
+ const collection = db.getCollection(v);
29
+ if (!collection) return null;
30
+ return `'${collection.tableNameAsString()}'::regclass`;
31
+ }).filter(Boolean);
32
+ };
33
+ const joinValues = items => items.join(', ');
18
34
  var _default = {
19
35
  $childIn(values, ctx) {
20
36
  const db = ctx.db;
21
- return {
22
- [_sequelize().Op.in]: mapVal(values, db)
23
- };
37
+ const items = filterItems(values, db);
38
+ if (items.length) {
39
+ return _sequelize().Sequelize.literal(`"${ctx.model.name}"."tableoid" IN (${joinValues(items)})`);
40
+ } else {
41
+ return _sequelize().Sequelize.literal(`1 = 2`);
42
+ }
24
43
  },
25
44
  $childNotIn(values, ctx) {
26
45
  const db = ctx.db;
27
- return {
28
- [_sequelize().Op.notIn]: mapVal(values, db)
29
- };
46
+ const items = filterItems(values, db);
47
+ if (items.length) {
48
+ return _sequelize().Sequelize.literal(`"${ctx.model.name}"."tableoid" NOT IN (${joinValues(items)})`);
49
+ } else {
50
+ return _sequelize().Sequelize.literal(`1 = 1`);
51
+ }
30
52
  }
31
53
  };
32
54
  exports.default = _default;
package/lib/repository.js CHANGED
@@ -4,6 +4,13 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.Repository = void 0;
7
+ function _flat() {
8
+ const data = require("flat");
9
+ _flat = function _flat() {
10
+ return data;
11
+ };
12
+ return data;
13
+ }
7
14
  function _lodash() {
8
15
  const data = _interopRequireDefault(require("lodash"));
9
16
  _lodash = function _lodash() {
@@ -20,6 +27,7 @@ function _sequelize() {
20
27
  }
21
28
  var _mustHaveFilterDecorator = _interopRequireDefault(require("./decorators/must-have-filter-decorator"));
22
29
  var _transactionDecorator = require("./decorators/transaction-decorator");
30
+ var _eagerLoadingTree = require("./eager-loading/eager-loading-tree");
23
31
  var _arrayFieldRepository = require("./field-repository/array-field-repository");
24
32
  var _fields = require("./fields");
25
33
  var _filterParser = _interopRequireDefault(require("./filter-parser"));
@@ -30,14 +38,6 @@ var _hasmanyRepository = require("./relation-repository/hasmany-repository");
30
38
  var _hasoneRepository = require("./relation-repository/hasone-repository");
31
39
  var _updateAssociations = require("./update-associations");
32
40
  var _updateGuard = require("./update-guard");
33
- var _eagerLoadingTree = require("./eager-loading/eager-loading-tree");
34
- function _flat() {
35
- const data = require("flat");
36
- _flat = function _flat() {
37
- return data;
38
- };
39
- return data;
40
- }
41
41
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
42
42
  function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
43
43
  function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
@@ -12,8 +12,6 @@ exports.updateModelByValues = updateModelByValues;
12
12
  exports.updateMultipleAssociation = updateMultipleAssociation;
13
13
  exports.updateSingleAssociation = updateSingleAssociation;
14
14
  exports.updateThroughTableValue = updateThroughTableValue;
15
- var _model = require("./model");
16
- var _updateGuard = require("./update-guard");
17
15
  function _lodash() {
18
16
  const data = _interopRequireDefault(require("lodash"));
19
17
  _lodash = function _lodash() {
@@ -21,6 +19,8 @@ function _lodash() {
21
19
  };
22
20
  return data;
23
21
  }
22
+ var _model = require("./model");
23
+ var _updateGuard = require("./update-guard");
24
24
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
25
25
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
26
26
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
@@ -367,6 +367,10 @@ function _updateMultipleAssociation() {
367
367
  if (!association) {
368
368
  return false;
369
369
  }
370
+ // @ts-ignore skip update association if through model is a view
371
+ if (association.through && association.through.model.options.view) {
372
+ return false;
373
+ }
370
374
  if (!['undefined', 'string', 'number', 'object'].includes(typeof value)) {
371
375
  return false;
372
376
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@nocobase/database",
3
- "version": "0.10.0-alpha.4",
3
+ "version": "0.10.1-alpha.1",
4
4
  "description": "",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
7
7
  "license": "Apache-2.0",
8
8
  "dependencies": {
9
- "@nocobase/logger": "0.10.0-alpha.4",
10
- "@nocobase/utils": "0.10.0-alpha.4",
9
+ "@nocobase/logger": "0.10.1-alpha.1",
10
+ "@nocobase/utils": "0.10.1-alpha.1",
11
11
  "async-mutex": "^0.3.2",
12
12
  "cron-parser": "4.4.0",
13
13
  "deepmerge": "^4.2.2",
@@ -28,5 +28,5 @@
28
28
  "url": "git+https://github.com/nocobase/nocobase.git",
29
29
  "directory": "packages/database"
30
30
  },
31
- "gitHead": "62dacadb2a83d30cb36dda9074f2f3a072a37484"
31
+ "gitHead": "8f415f5e0ee2e72d681f9ab16af5911b52c374a9"
32
32
  }
@@ -1,7 +1,7 @@
1
+ import { BelongsToManyRepository } from '@nocobase/database';
1
2
  import Database from '../../database';
2
3
  import { InheritedCollection } from '../../inherited-collection';
3
4
  import { mockDatabase } from '../index';
4
- import { BelongsToManyRepository } from '@nocobase/database';
5
5
  import pgOnly from './helper';
6
6
 
7
7
  pgOnly()('collection inherits', () => {
@@ -182,9 +182,17 @@ pgOnly()('collection inherits', () => {
182
182
  });
183
183
 
184
184
  it('should list data filtered by child type', async () => {
185
+ const assocs = db.collection({
186
+ name: 'assocs',
187
+ fields: [{ name: 'name', type: 'string' }],
188
+ });
189
+
185
190
  const rootCollection = db.collection({
186
191
  name: 'root',
187
- fields: [{ name: 'name', type: 'string' }],
192
+ fields: [
193
+ { name: 'name', type: 'string' },
194
+ { name: 'assocs', type: 'hasMany', target: 'assocs' },
195
+ ],
188
196
  });
189
197
 
190
198
  const child1Collection = db.collection({
@@ -202,11 +210,29 @@ pgOnly()('collection inherits', () => {
202
210
  await rootCollection.repository.create({
203
211
  values: {
204
212
  name: 'root1',
213
+ assocs: [
214
+ {
215
+ name: 'assoc1',
216
+ },
217
+ ],
205
218
  },
206
219
  });
207
220
 
208
221
  await child1Collection.repository.create({
209
- values: [{ name: 'child1-1' }, { name: 'child1-2' }],
222
+ values: [
223
+ {
224
+ name: 'child1-1',
225
+ assocs: [
226
+ {
227
+ name: 'child-assoc1-1',
228
+ },
229
+ ],
230
+ },
231
+ {
232
+ name: 'child1-2',
233
+ assocs: [{ name: 'child-assoc1-2' }],
234
+ },
235
+ ],
210
236
  });
211
237
 
212
238
  await child2Collection.repository.create({
@@ -215,18 +241,38 @@ pgOnly()('collection inherits', () => {
215
241
 
216
242
  const records = await rootCollection.repository.find({
217
243
  filter: {
218
- 'tableoid.$childIn': [child1Collection.name],
244
+ '__collection.$childIn': [child1Collection.name],
219
245
  },
246
+ appends: ['assocs'],
220
247
  });
221
248
 
222
249
  expect(records.every((r) => r.get('__collection') === child1Collection.name)).toBe(true);
223
250
 
224
251
  const records2 = await rootCollection.repository.find({
225
252
  filter: {
226
- 'tableoid.$childNotIn': [child1Collection.name],
253
+ '__collection.$childNotIn': [child1Collection.name],
227
254
  },
228
255
  });
229
256
  expect(records2.every((r) => r.get('__collection') !== child1Collection.name)).toBe(true);
257
+
258
+ const recordsWithFilter = await rootCollection.repository.find({
259
+ filter: {
260
+ '__collection.$childIn': [child1Collection.name],
261
+ assocs: {
262
+ name: 'child-assoc1-1',
263
+ },
264
+ },
265
+ });
266
+
267
+ expect(recordsWithFilter.every((r) => r.get('__collection') == child1Collection.name)).toBe(true);
268
+
269
+ const filterWithUndefined = await rootCollection.repository.find({
270
+ filter: {
271
+ '__collection.$childIn': 'undefined',
272
+ },
273
+ });
274
+
275
+ expect(filterWithUndefined).toHaveLength(0);
230
276
  });
231
277
 
232
278
  it('should list collection name in relation repository', async () => {
@@ -3,6 +3,185 @@ import { Database } from '../database';
3
3
  import { updateAssociations } from '../update-associations';
4
4
  import { mockDatabase } from './';
5
5
 
6
+ describe('update belongs to many with view as through table', () => {
7
+ let db: Database;
8
+ beforeEach(async () => {
9
+ db = mockDatabase({
10
+ tablePrefix: '',
11
+ });
12
+ await db.clean({
13
+ drop: true,
14
+ });
15
+ });
16
+
17
+ afterEach(async () => {
18
+ await db.close();
19
+ });
20
+
21
+ it('should not update through table', async () => {
22
+ const Order = db.collection({
23
+ name: 'orders',
24
+ fields: [
25
+ {
26
+ type: 'string',
27
+ name: 'name',
28
+ },
29
+ {
30
+ type: 'hasMany',
31
+ name: 'orderItems',
32
+ foreignKey: 'order_id',
33
+ target: 'orderItems',
34
+ },
35
+ ],
36
+ });
37
+
38
+ const OrderItem = db.collection({
39
+ name: 'orderItems',
40
+ timestamps: false,
41
+ fields: [
42
+ {
43
+ type: 'integer',
44
+ name: 'count',
45
+ },
46
+ {
47
+ type: 'belongsTo',
48
+ name: 'item',
49
+ target: 'items',
50
+ foreignKey: 'item_id',
51
+ },
52
+ {
53
+ type: 'belongsTo',
54
+ name: 'order',
55
+ target: 'orders',
56
+ foreignKey: 'order_id',
57
+ },
58
+ ],
59
+ });
60
+
61
+ const Item = db.collection({
62
+ name: 'items',
63
+ fields: [{ name: 'name', type: 'string' }],
64
+ });
65
+
66
+ await db.sync();
67
+
68
+ const viewName = 'order_item_view';
69
+
70
+ const dropViewSQL = `DROP VIEW IF EXISTS ${viewName}`;
71
+ await db.sequelize.query(dropViewSQL);
72
+
73
+ const viewSQL = `CREATE VIEW ${viewName} as SELECT orders.*, items.name as item_name FROM ${OrderItem.quotedTableName()} as orders INNER JOIN ${Item.quotedTableName()} as items ON orders.item_id = items.id`;
74
+
75
+ await db.sequelize.query(viewSQL);
76
+
77
+ const OrderItemView = db.collection({
78
+ name: viewName,
79
+ view: true,
80
+ schema: db.inDialect('postgres') ? 'public' : undefined,
81
+ fields: [
82
+ {
83
+ type: 'bigInt',
84
+ name: 'order_id',
85
+ },
86
+ {
87
+ type: 'bigInt',
88
+ name: 'item_id',
89
+ },
90
+ ],
91
+ });
92
+
93
+ await db.sync();
94
+
95
+ Order.setField('items', {
96
+ type: 'belongsToMany',
97
+ target: 'orderItems',
98
+ through: viewName,
99
+ foreignKey: 'order_id',
100
+ otherKey: 'item_id',
101
+ sourceKey: 'id',
102
+ targetKey: 'id',
103
+ });
104
+
105
+ await db.sync();
106
+
107
+ const order1 = await db.getRepository('orders').create({
108
+ values: {
109
+ name: 'order1',
110
+ orderItems: [
111
+ {
112
+ count: 1,
113
+ item: {
114
+ name: 'item1',
115
+ },
116
+ },
117
+ {
118
+ count: 2,
119
+ item: {
120
+ name: 'item2',
121
+ },
122
+ },
123
+ ],
124
+ },
125
+ });
126
+
127
+ const item1 = await db.getRepository('items').findOne({
128
+ filter: {
129
+ name: 'item1',
130
+ },
131
+ });
132
+
133
+ const item2 = await db.getRepository('items').findOne({
134
+ filter: {
135
+ name: 'item2',
136
+ },
137
+ });
138
+
139
+ await db.getRepository('orders').update({
140
+ filterByTk: order1.get('id'),
141
+ values: {
142
+ name: 'order1',
143
+ orderItems: [
144
+ {
145
+ count: 1,
146
+ item: {
147
+ id: item1.get('id'),
148
+ },
149
+ },
150
+ {
151
+ count: 2,
152
+ item: {
153
+ id: item2.get('id'),
154
+ },
155
+ },
156
+ {
157
+ count: 3,
158
+ item: {
159
+ name: 'item3',
160
+ },
161
+ },
162
+ ],
163
+ items: [
164
+ {
165
+ id: item1.get('id'),
166
+ },
167
+ {
168
+ id: item2.get('id'),
169
+ },
170
+ ],
171
+ },
172
+ });
173
+
174
+ const order1AfterUpdate = await db.getRepository('orders').findOne({
175
+ filter: {
176
+ name: 'order1',
177
+ },
178
+ appends: ['items'],
179
+ });
180
+
181
+ expect(order1AfterUpdate.get('items').length).toBe(3);
182
+ });
183
+ });
184
+
6
185
  describe('update associations', () => {
7
186
  describe('belongsTo', () => {
8
187
  let db: Database;
@@ -9,7 +9,7 @@ export class UidField extends Field {
9
9
 
10
10
  init() {
11
11
  const { name, prefix = '', pattern } = this.options;
12
- const re = new RegExp(pattern || '^[A-Za-z0-9][A-Za-z0-9_-]*$');
12
+ const re = new RegExp(pattern || '^[A-Za-z0-9_][A-Za-z0-9_-]*$');
13
13
  this.listener = async (instance) => {
14
14
  const value = instance.get(name);
15
15
  if (!value) {
@@ -226,6 +226,7 @@ export default class FilterParser {
226
226
  if (values && typeof values === 'object' && value && typeof value === 'object') {
227
227
  value = { ...value, ...values };
228
228
  }
229
+
229
230
  _.set(where, paths, value);
230
231
  }
231
232
 
package/src/index.ts CHANGED
@@ -23,3 +23,4 @@ export * from './value-parsers';
23
23
  export * from './collection-group-manager';
24
24
  export * from './view-collection';
25
25
  export * from './view/view-inference';
26
+ export { default as FilterParser } from './filter-parser';
@@ -1,4 +1,5 @@
1
- import { Op, Sequelize } from 'sequelize';
1
+ import lodash from 'lodash';
2
+ import { Sequelize } from 'sequelize';
2
3
 
3
4
  const mapVal = (values, db) =>
4
5
  values.map((v) => {
@@ -6,19 +7,37 @@ const mapVal = (values, db) =>
6
7
  return Sequelize.literal(`'${collection.tableNameAsString()}'::regclass`);
7
8
  });
8
9
 
10
+ const filterItems = (values, db) => {
11
+ return lodash
12
+ .castArray(values)
13
+ .map((v) => {
14
+ const collection = db.getCollection(v);
15
+ if (!collection) return null;
16
+ return `'${collection.tableNameAsString()}'::regclass`;
17
+ })
18
+ .filter(Boolean);
19
+ };
20
+ const joinValues = (items) => items.join(', ');
9
21
  export default {
10
22
  $childIn(values, ctx: any) {
11
23
  const db = ctx.db;
24
+ const items = filterItems(values, db);
12
25
 
13
- return {
14
- [Op.in]: mapVal(values, db),
15
- };
26
+ if (items.length) {
27
+ return Sequelize.literal(`"${ctx.model.name}"."tableoid" IN (${joinValues(items)})`);
28
+ } else {
29
+ return Sequelize.literal(`1 = 2`);
30
+ }
16
31
  },
32
+
17
33
  $childNotIn(values, ctx: any) {
18
34
  const db = ctx.db;
35
+ const items = filterItems(values, db);
19
36
 
20
- return {
21
- [Op.notIn]: mapVal(values, db),
22
- };
37
+ if (items.length) {
38
+ return Sequelize.literal(`"${ctx.model.name}"."tableoid" NOT IN (${joinValues(items)})`);
39
+ } else {
40
+ return Sequelize.literal(`1 = 1`);
41
+ }
23
42
  },
24
43
  } as Record<string, any>;
package/src/repository.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { flatten } from 'flat';
1
2
  import lodash from 'lodash';
2
3
  import {
3
4
  Association,
@@ -18,6 +19,7 @@ import { Collection } from './collection';
18
19
  import { Database } from './database';
19
20
  import mustHaveFilter from './decorators/must-have-filter-decorator';
20
21
  import { transactionWrapperBuilder } from './decorators/transaction-decorator';
22
+ import { EagerLoadingTree } from './eager-loading/eager-loading-tree';
21
23
  import { ArrayFieldRepository } from './field-repository/array-field-repository';
22
24
  import { ArrayField, RelationField } from './fields';
23
25
  import FilterParser from './filter-parser';
@@ -31,8 +33,6 @@ import { HasOneRepository } from './relation-repository/hasone-repository';
31
33
  import { RelationRepository } from './relation-repository/relation-repository';
32
34
  import { updateAssociations, updateModelByValues } from './update-associations';
33
35
  import { UpdateGuard } from './update-guard';
34
- import { EagerLoadingTree } from './eager-loading/eager-loading-tree';
35
- import { flatten } from 'flat';
36
36
 
37
37
  const debug = require('debug')('noco-database');
38
38
 
@@ -1,3 +1,4 @@
1
+ import lodash from 'lodash';
1
2
  import {
2
3
  Association,
3
4
  BelongsTo,
@@ -10,7 +11,6 @@ import {
10
11
  } from 'sequelize';
11
12
  import { Model } from './model';
12
13
  import { UpdateGuard } from './update-guard';
13
- import lodash from 'lodash';
14
14
 
15
15
  function isUndefinedOrNull(value: any) {
16
16
  return typeof value === 'undefined' || value === null;
@@ -377,6 +377,11 @@ export async function updateMultipleAssociation(
377
377
  return false;
378
378
  }
379
379
 
380
+ // @ts-ignore skip update association if through model is a view
381
+ if (association.through && association.through.model.options.view) {
382
+ return false;
383
+ }
384
+
380
385
  if (!['undefined', 'string', 'number', 'object'].includes(typeof value)) {
381
386
  return false;
382
387
  }