@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.
- package/lib/collection-group-manager.d.ts +13 -0
- package/lib/collection-group-manager.js +91 -0
- package/lib/collection-importer.js +0 -24
- package/lib/collection.d.ts +26 -4
- package/lib/collection.js +190 -234
- package/lib/database-utils/index.js +3 -15
- package/lib/database.d.ts +9 -0
- package/lib/database.js +171 -299
- package/lib/decorators/must-have-filter-decorator.js +0 -7
- package/lib/decorators/transaction-decorator.js +5 -18
- package/lib/errors/identifier-error.js +0 -3
- package/lib/features/ReferencesMap.js +1 -14
- package/lib/features/referential-integrity-check.js +7 -21
- package/lib/field-repository/array-field-repository.js +5 -45
- package/lib/fields/array-field.js +0 -13
- package/lib/fields/belongs-to-field.js +24 -50
- package/lib/fields/belongs-to-many-field.js +29 -47
- package/lib/fields/boolean-field.js +0 -7
- package/lib/fields/context-field.js +2 -23
- package/lib/fields/date-field.d.ts +4 -0
- package/lib/fields/date-field.js +15 -7
- package/lib/fields/field.js +32 -85
- package/lib/fields/has-many-field.js +16 -49
- package/lib/fields/has-one-field.js +18 -52
- package/lib/fields/index.js +0 -44
- package/lib/fields/json-field.js +0 -12
- package/lib/fields/number-field.js +0 -23
- package/lib/fields/password-field.js +8 -35
- package/lib/fields/radio-field.js +0 -18
- package/lib/fields/relation-field.js +4 -16
- package/lib/fields/set-field.js +0 -8
- package/lib/fields/sort-field.js +84 -73
- package/lib/fields/string-field.js +0 -7
- package/lib/fields/text-field.js +0 -7
- package/lib/fields/time-field.js +0 -7
- package/lib/fields/uid-field.js +4 -22
- package/lib/fields/uuid-field.js +3 -12
- package/lib/fields/virtual-field.js +0 -7
- package/lib/filter-match.js +7 -22
- package/lib/filter-parser.js +38 -102
- package/lib/index.d.ts +3 -0
- package/lib/index.js +36 -42
- package/lib/inherited-collection.js +15 -62
- package/lib/inherited-map.js +7 -48
- package/lib/listeners/adjacency-list.d.ts +3 -0
- package/lib/listeners/adjacency-list.js +91 -0
- package/lib/listeners/index.d.ts +2 -0
- package/lib/listeners/index.js +12 -0
- package/lib/magic-attribute-model.js +58 -114
- package/lib/migration.js +7 -28
- package/lib/mock-database.d.ts +4 -4
- package/lib/mock-database.js +15 -18
- package/lib/model-hook.js +4 -35
- package/lib/model.js +12 -54
- package/lib/operators/array.js +2 -32
- package/lib/operators/association.js +0 -6
- package/lib/operators/boolean.js +0 -6
- package/lib/operators/child-collection.d.ts +2 -0
- package/lib/operators/child-collection.js +32 -0
- package/lib/operators/date.js +123 -60
- package/lib/operators/empty.js +3 -32
- package/lib/operators/eq.d.ts +2 -0
- package/lib/operators/eq.js +26 -0
- package/lib/operators/index.js +4 -7
- package/lib/operators/ne.js +5 -5
- package/lib/operators/notIn.js +0 -5
- package/lib/operators/string.js +0 -11
- package/lib/operators/utils.js +0 -6
- package/lib/options-parser.d.ts +1 -1
- package/lib/options-parser.js +47 -107
- package/lib/playground.js +0 -4
- package/lib/query-interface/mysql-query-interface.d.ts +18 -0
- package/lib/query-interface/mysql-query-interface.js +88 -0
- package/lib/query-interface/postgres-query-interface.d.ts +14 -0
- package/lib/query-interface/postgres-query-interface.js +99 -0
- package/lib/query-interface/query-interface-builder.d.ts +2 -0
- package/lib/query-interface/query-interface-builder.js +18 -0
- package/lib/query-interface/query-interface.d.ts +21 -0
- package/lib/query-interface/query-interface.js +48 -0
- package/lib/query-interface/sqlite-query-interface.d.ts +17 -0
- package/lib/query-interface/sqlite-query-interface.js +89 -0
- package/lib/relation-repository/belongs-to-many-repository.js +21 -78
- package/lib/relation-repository/belongs-to-repository.js +0 -3
- package/lib/relation-repository/hasmany-repository.js +8 -44
- package/lib/relation-repository/hasone-repository.js +0 -3
- package/lib/relation-repository/multiple-relation-repository.js +16 -68
- package/lib/relation-repository/relation-repository.js +5 -42
- package/lib/relation-repository/single-relation-repository.js +6 -43
- package/lib/repository.d.ts +1 -0
- package/lib/repository.js +36 -182
- package/lib/sql-parser/index.js +10527 -0
- package/lib/sql-parser/sql.pegjs +1297 -0
- package/lib/sync-runner.d.ts +1 -1
- package/lib/sync-runner.js +26 -64
- package/lib/update-associations.js +58 -157
- package/lib/update-guard.js +10 -49
- package/lib/utils.js +16 -54
- package/lib/value-parsers/array-value-parser.js +3 -21
- package/lib/value-parsers/base-value-parser.js +0 -13
- package/lib/value-parsers/boolean-value-parser.js +4 -10
- package/lib/value-parsers/date-value-parser.js +0 -23
- package/lib/value-parsers/index.js +0 -10
- package/lib/value-parsers/json-value-parser.js +0 -7
- package/lib/value-parsers/number-value-parser.js +0 -9
- package/lib/value-parsers/string-value-parser.js +3 -20
- package/lib/value-parsers/to-many-value-parser.js +1 -42
- package/lib/value-parsers/to-one-value-parser.js +0 -14
- package/lib/view/field-type-map.d.ts +47 -0
- package/lib/view/field-type-map.js +56 -0
- package/lib/view/view-inference.d.ts +31 -0
- package/lib/view/view-inference.js +92 -0
- package/lib/view-collection.d.ts +6 -0
- package/lib/view-collection.js +24 -0
- package/package.json +4 -3
- package/src/__tests__/collection.test.ts +44 -0
- package/src/__tests__/fields/date.test.ts +75 -0
- package/src/__tests__/fields/sort-field.test.ts +100 -0
- package/src/__tests__/filter.test.ts +60 -0
- package/src/__tests__/group.test.ts +50 -0
- package/src/__tests__/inhertits/collection-inherits.test.ts +114 -0
- package/src/__tests__/operator/date-operator.test.ts +244 -98
- package/src/__tests__/operator/eq.test.ts +76 -0
- package/src/__tests__/operator/ne.test.ts +19 -1
- package/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +82 -0
- package/src/__tests__/repository/find.test.ts +33 -0
- package/src/__tests__/repository.test.ts +88 -0
- package/src/__tests__/sql-parser.test.ts +13 -0
- package/src/__tests__/tree.test.ts +217 -0
- package/src/__tests__/view/list-view.test.ts +34 -0
- package/src/__tests__/view/view-collection.test.ts +199 -0
- package/src/__tests__/view/view-inference.test.ts +145 -0
- package/src/__tests__/view/view-repository.test.ts +67 -0
- package/src/collection-group-manager.ts +94 -0
- package/src/collection.ts +126 -16
- package/src/database-utils/index.ts +1 -0
- package/src/database.ts +98 -17
- package/src/features/ReferencesMap.ts +3 -2
- package/src/fields/belongs-to-many-field.ts +23 -4
- package/src/fields/date-field.ts +18 -0
- package/src/fields/field.ts +17 -7
- package/src/fields/json-field.ts +1 -0
- package/src/fields/sort-field.ts +90 -29
- package/src/filter-parser.ts +2 -1
- package/src/index.ts +3 -1
- package/src/listeners/adjacency-list.ts +60 -0
- package/src/listeners/index.ts +7 -0
- package/src/mock-database.ts +14 -2
- package/src/model.ts +4 -0
- package/src/operators/child-collection.ts +24 -0
- package/src/operators/date.ts +108 -24
- package/src/operators/eq.ts +14 -0
- package/src/operators/index.ts +2 -0
- package/src/operators/ne.ts +12 -7
- package/src/options-parser.ts +25 -11
- package/src/query-interface/mysql-query-interface.ts +72 -0
- package/src/query-interface/postgres-query-interface.ts +103 -0
- package/src/query-interface/query-interface-builder.ts +14 -0
- package/src/query-interface/query-interface.ts +43 -0
- package/src/query-interface/sqlite-query-interface.ts +79 -0
- package/src/relation-repository/belongs-to-many-repository.ts +20 -1
- package/src/relation-repository/hasmany-repository.ts +5 -3
- package/src/relation-repository/multiple-relation-repository.ts +13 -1
- package/src/relation-repository/single-relation-repository.ts +2 -0
- package/src/repository.ts +6 -13
- package/src/sql-parser/index.js +10698 -0
- package/src/sql-parser/readme.md +2 -0
- package/src/sql-parser/sql.pegjs +1297 -0
- package/src/sync-runner.ts +27 -32
- package/src/update-associations.ts +26 -22
- package/src/utils.ts +4 -3
- package/src/view/field-type-map.ts +56 -0
- package/src/view/view-inference.ts +106 -0
- package/src/view-collection.ts +21 -0
|
@@ -32,9 +32,21 @@ export class BelongsToManyField extends RelationField {
|
|
|
32
32
|
|
|
33
33
|
const onDelete = this.options.onDelete || 'CASCADE';
|
|
34
34
|
|
|
35
|
+
const targetAssociation = association.toTarget;
|
|
36
|
+
|
|
37
|
+
if (association.targetKey) {
|
|
38
|
+
targetAssociation.targetKey = association.targetKey;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const sourceAssociation = association.toSource;
|
|
42
|
+
|
|
43
|
+
if (association.sourceKey) {
|
|
44
|
+
sourceAssociation.targetKey = association.sourceKey;
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
return [
|
|
36
|
-
BelongsToField.toReference(db,
|
|
37
|
-
BelongsToField.toReference(db,
|
|
48
|
+
BelongsToField.toReference(db, targetAssociation, onDelete),
|
|
49
|
+
BelongsToField.toReference(db, sourceAssociation, onDelete),
|
|
38
50
|
];
|
|
39
51
|
}
|
|
40
52
|
|
|
@@ -55,9 +67,16 @@ export class BelongsToManyField extends RelationField {
|
|
|
55
67
|
if (database.hasCollection(through)) {
|
|
56
68
|
Through = database.getCollection(through);
|
|
57
69
|
} else {
|
|
58
|
-
|
|
70
|
+
const throughCollectionOptions = {
|
|
59
71
|
name: through,
|
|
60
|
-
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// set through collection schema
|
|
75
|
+
if (this.collection.collectionSchema()) {
|
|
76
|
+
throughCollectionOptions['schema'] = this.collection.collectionSchema();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Through = database.collection(throughCollectionOptions);
|
|
61
80
|
|
|
62
81
|
Object.defineProperty(Through.model, 'isThrough', { value: true });
|
|
63
82
|
}
|
package/src/fields/date-field.ts
CHANGED
|
@@ -5,6 +5,24 @@ export class DateField extends Field {
|
|
|
5
5
|
get dataType() {
|
|
6
6
|
return DataTypes.DATE(3);
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
get timezone() {
|
|
10
|
+
return this.isGMT() ? '+00:00' : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getProps() {
|
|
14
|
+
return this.options?.uiSchema?.['x-component-props'] || {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
isDateOnly() {
|
|
18
|
+
const props = this.getProps();
|
|
19
|
+
return !props.showTime;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
isGMT() {
|
|
23
|
+
const props = this.getProps();
|
|
24
|
+
return props.gmt;
|
|
25
|
+
}
|
|
8
26
|
}
|
|
9
27
|
|
|
10
28
|
export interface DateFieldOptions extends BaseColumnFieldOptions {
|
package/src/fields/field.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
ModelIndexesOptions,
|
|
7
7
|
QueryInterfaceOptions,
|
|
8
8
|
SyncOptions,
|
|
9
|
-
Transactionable
|
|
9
|
+
Transactionable,
|
|
10
10
|
} from 'sequelize';
|
|
11
11
|
import { Collection } from '../collection';
|
|
12
12
|
import { Database } from '../database';
|
|
@@ -157,6 +157,11 @@ export abstract class Field {
|
|
|
157
157
|
// return;
|
|
158
158
|
// }
|
|
159
159
|
|
|
160
|
+
if (this.collection.isView()) {
|
|
161
|
+
this.remove();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
160
165
|
const columnReferencesCount = _.filter(
|
|
161
166
|
this.collection.model.rawAttributes,
|
|
162
167
|
(attr) => attr.field == this.columnName(),
|
|
@@ -169,7 +174,7 @@ export abstract class Field {
|
|
|
169
174
|
columnReferencesCount == 1
|
|
170
175
|
) {
|
|
171
176
|
const queryInterface = this.database.sequelize.getQueryInterface();
|
|
172
|
-
await queryInterface.removeColumn(this.collection.
|
|
177
|
+
await queryInterface.removeColumn(this.collection.getTableNameWithSchema(), this.columnName(), options);
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
this.remove();
|
|
@@ -181,20 +186,24 @@ export abstract class Field {
|
|
|
181
186
|
};
|
|
182
187
|
let sql;
|
|
183
188
|
if (this.database.sequelize.getDialect() === 'sqlite') {
|
|
184
|
-
sql = `SELECT *
|
|
189
|
+
sql = `SELECT *
|
|
190
|
+
from pragma_table_info('${this.collection.model.tableName}')
|
|
191
|
+
WHERE name = '${this.columnName()}'`;
|
|
185
192
|
} else if (this.database.inDialect('mysql')) {
|
|
186
193
|
sql = `
|
|
187
194
|
select column_name
|
|
188
195
|
from INFORMATION_SCHEMA.COLUMNS
|
|
189
|
-
where TABLE_SCHEMA='${this.database.options.database}'
|
|
190
|
-
|
|
191
|
-
|
|
196
|
+
where TABLE_SCHEMA = '${this.database.options.database}'
|
|
197
|
+
AND TABLE_NAME = '${this.collection.model.tableName}'
|
|
198
|
+
AND column_name = '${this.columnName()}'
|
|
192
199
|
`;
|
|
193
200
|
} else {
|
|
194
201
|
sql = `
|
|
195
202
|
select column_name
|
|
196
203
|
from INFORMATION_SCHEMA.COLUMNS
|
|
197
|
-
where TABLE_NAME='${this.collection.model.tableName}'
|
|
204
|
+
where TABLE_NAME = '${this.collection.model.tableName}'
|
|
205
|
+
AND column_name = '${this.columnName()}'
|
|
206
|
+
AND table_schema = '${this.collection.collectionSchema() || 'public'}'
|
|
198
207
|
`;
|
|
199
208
|
}
|
|
200
209
|
const [rows] = await this.database.sequelize.query(sql, opts);
|
|
@@ -228,6 +237,7 @@ export abstract class Field {
|
|
|
228
237
|
if (this.dataType) {
|
|
229
238
|
Object.assign(opts, { type: this.dataType });
|
|
230
239
|
}
|
|
240
|
+
|
|
231
241
|
return opts;
|
|
232
242
|
}
|
|
233
243
|
|
package/src/fields/json-field.ts
CHANGED
package/src/fields/sort-field.ts
CHANGED
|
@@ -42,49 +42,110 @@ export class SortField extends Field {
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
initRecordsSortValue = async ({ transaction }) => {
|
|
45
|
-
const
|
|
46
|
-
transaction,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const emptyCount = await this.collection.repository.count({
|
|
50
|
-
filter: {
|
|
51
|
-
[this.name]: null,
|
|
52
|
-
},
|
|
53
|
-
transaction,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const orderKey = (() => {
|
|
45
|
+
const orderField = (() => {
|
|
57
46
|
const model = this.collection.model;
|
|
58
47
|
if (model.primaryKeyAttribute) {
|
|
59
48
|
return model.primaryKeyAttribute;
|
|
60
49
|
}
|
|
61
50
|
if (model.rawAttributes['createdAt']) {
|
|
62
|
-
return 'createdAt';
|
|
51
|
+
return model.rawAttributes['createdAt'].field;
|
|
63
52
|
}
|
|
64
53
|
|
|
65
54
|
throw new Error(`can not find order key for collection ${this.collection.name}`);
|
|
66
55
|
})();
|
|
67
56
|
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
57
|
+
const needInit = async (scopeKey = null, scopeValue = null) => {
|
|
58
|
+
const filter = {};
|
|
59
|
+
if (scopeKey && scopeValue) {
|
|
60
|
+
filter[scopeKey] = scopeValue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const totalCount = await this.collection.repository.count({
|
|
64
|
+
filter,
|
|
65
|
+
transaction,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const emptyCount = await this.collection.repository.count({
|
|
69
|
+
filter: {
|
|
70
|
+
[this.name]: null,
|
|
71
|
+
...filter,
|
|
72
|
+
},
|
|
71
73
|
transaction,
|
|
72
74
|
});
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
76
|
+
return emptyCount === totalCount && emptyCount > 0;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const doInit = async (scopeKey = null, scopeValue = null) => {
|
|
80
|
+
const queryInterface = this.collection.db.sequelize.getQueryInterface();
|
|
81
|
+
|
|
82
|
+
const quotedOrderField = queryInterface.quoteIdentifier(orderField);
|
|
83
|
+
|
|
84
|
+
const sql = `
|
|
85
|
+
WITH ordered_table AS (
|
|
86
|
+
SELECT *, ROW_NUMBER() OVER (${
|
|
87
|
+
scopeKey ? `PARTITION BY ${queryInterface.quoteIdentifier(scopeKey)}` : ''
|
|
88
|
+
} ORDER BY ${quotedOrderField}) AS new_sequence_number
|
|
89
|
+
FROM ${this.collection.quotedTableName()}
|
|
90
|
+
${(() => {
|
|
91
|
+
if (scopeKey && scopeValue) {
|
|
92
|
+
const hasNull = scopeValue.includes(null);
|
|
93
|
+
|
|
94
|
+
return `WHERE ${queryInterface.quoteIdentifier(scopeKey)} IN (${scopeValue
|
|
95
|
+
.filter((v) => v !== null)
|
|
96
|
+
.map((v) => `'${v}'`)
|
|
97
|
+
.join(',')}) ${hasNull ? `OR ${queryInterface.quoteIdentifier(scopeKey)} IS NULL` : ''} `;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return '';
|
|
101
|
+
})()}
|
|
102
|
+
|
|
103
|
+
)
|
|
104
|
+
${
|
|
105
|
+
this.collection.db.inDialect('mysql')
|
|
106
|
+
? `
|
|
107
|
+
UPDATE ${this.collection.quotedTableName()}, ordered_table
|
|
108
|
+
SET ${this.collection.quotedTableName()}.${this.name} = ordered_table.new_sequence_number
|
|
109
|
+
WHERE ${this.collection.quotedTableName()}.${quotedOrderField} = ordered_table.${quotedOrderField}
|
|
110
|
+
`
|
|
111
|
+
: `
|
|
112
|
+
UPDATE ${this.collection.quotedTableName()}
|
|
113
|
+
SET ${queryInterface.quoteIdentifier(this.name)} = ordered_table.new_sequence_number
|
|
114
|
+
FROM ordered_table
|
|
115
|
+
WHERE ${this.collection.quotedTableName()}.${quotedOrderField} = ${queryInterface.quoteIdentifier(
|
|
116
|
+
'ordered_table',
|
|
117
|
+
)}.${quotedOrderField};
|
|
118
|
+
`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
await this.collection.db.sequelize.query(sql, {
|
|
124
|
+
transaction,
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const scopeKey = this.options.scopeKey;
|
|
129
|
+
if (scopeKey) {
|
|
130
|
+
const groups = await this.collection.repository.find({
|
|
131
|
+
attributes: [scopeKey],
|
|
132
|
+
group: [scopeKey],
|
|
133
|
+
raw: true,
|
|
134
|
+
transaction,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const needInitGroups = [];
|
|
138
|
+
for (const group of groups) {
|
|
139
|
+
if (await needInit(scopeKey, group[scopeKey])) {
|
|
140
|
+
needInitGroups.push(group[scopeKey]);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (needInitGroups.length > 0) {
|
|
145
|
+
await doInit(scopeKey, needInitGroups);
|
|
87
146
|
}
|
|
147
|
+
} else if (await needInit()) {
|
|
148
|
+
await doInit();
|
|
88
149
|
}
|
|
89
150
|
};
|
|
90
151
|
|
package/src/filter-parser.ts
CHANGED
|
@@ -135,6 +135,7 @@ export default class FilterParser {
|
|
|
135
135
|
path: skipPrefix,
|
|
136
136
|
fullName,
|
|
137
137
|
fieldName,
|
|
138
|
+
fieldPath: `${this.collection.name}.${fullName}`,
|
|
138
139
|
model: this.model,
|
|
139
140
|
});
|
|
140
141
|
break;
|
|
@@ -178,7 +179,7 @@ export default class FilterParser {
|
|
|
178
179
|
origins.push(attr);
|
|
179
180
|
// if it is target model attribute
|
|
180
181
|
if (target.rawAttributes[attr]) {
|
|
181
|
-
associationKeys.push(attr);
|
|
182
|
+
associationKeys.push(target.rawAttributes[attr].field || attr);
|
|
182
183
|
target = null;
|
|
183
184
|
} else if (target.associations[attr]) {
|
|
184
185
|
// if it is target model association (nested association filter)
|
package/src/index.ts
CHANGED
|
@@ -20,4 +20,6 @@ export * from './repository';
|
|
|
20
20
|
export * from './update-associations';
|
|
21
21
|
export { snakeCase } from './utils';
|
|
22
22
|
export * from './value-parsers';
|
|
23
|
-
|
|
23
|
+
export * from './collection-group-manager';
|
|
24
|
+
export * from './view-collection';
|
|
25
|
+
export * from './view/view-inference';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import lodash from 'lodash';
|
|
2
|
+
import { Collection, CollectionOptions } from '../collection';
|
|
3
|
+
import { Model } from '../model';
|
|
4
|
+
|
|
5
|
+
export const beforeDefineAdjacencyListCollection = (options: CollectionOptions) => {
|
|
6
|
+
if (!options.tree) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
(options.fields || []).forEach((field) => {
|
|
10
|
+
if (field.treeParent || field.treeChildren) {
|
|
11
|
+
if (!field.target) {
|
|
12
|
+
field.target = options.name;
|
|
13
|
+
}
|
|
14
|
+
if (!field.foreignKey) {
|
|
15
|
+
field.foreignKey = 'parentId';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const afterDefineAdjacencyListCollection = (collection: Collection) => {
|
|
22
|
+
if (!collection.options.tree) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
collection.model.afterFind(async (instances, options: any) => {
|
|
26
|
+
if (!options.tree) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const foreignKey = collection.treeParentField?.foreignKey ?? 'parentId';
|
|
30
|
+
const childrenKey = collection.treeChildrenField?.name ?? 'children';
|
|
31
|
+
const arr: Model[] = Array.isArray(instances) ? instances : [instances];
|
|
32
|
+
let index = 0;
|
|
33
|
+
for (const instance of arr) {
|
|
34
|
+
const opts = {
|
|
35
|
+
...lodash.pick(options, ['tree', 'fields', 'appends', 'except', 'sort']),
|
|
36
|
+
};
|
|
37
|
+
let __index = `${index++}`;
|
|
38
|
+
if (options.parentIndex) {
|
|
39
|
+
__index = `${options.parentIndex}.${__index}`;
|
|
40
|
+
}
|
|
41
|
+
instance.setDataValue('__index', __index);
|
|
42
|
+
const children = await collection.repository.find({
|
|
43
|
+
filter: {
|
|
44
|
+
[foreignKey]: instance.id,
|
|
45
|
+
},
|
|
46
|
+
transaction: options.transaction,
|
|
47
|
+
...opts,
|
|
48
|
+
// @ts-ignore
|
|
49
|
+
parentIndex: `${__index}.${childrenKey}`,
|
|
50
|
+
context: options.context,
|
|
51
|
+
});
|
|
52
|
+
if (children?.length > 0) {
|
|
53
|
+
instance.setDataValue(
|
|
54
|
+
childrenKey,
|
|
55
|
+
children.map((r) => r.toJSON()),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Database } from '../database';
|
|
2
|
+
import { afterDefineAdjacencyListCollection, beforeDefineAdjacencyListCollection } from './adjacency-list';
|
|
3
|
+
|
|
4
|
+
export const registerBuiltInListeners = (db: Database) => {
|
|
5
|
+
db.on('beforeDefineCollection', beforeDefineAdjacencyListCollection);
|
|
6
|
+
db.on('afterDefineCollection', afterDefineAdjacencyListCollection);
|
|
7
|
+
};
|
package/src/mock-database.ts
CHANGED
|
@@ -14,14 +14,14 @@ export class MockDatabase extends Database {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function getConfigByEnv() {
|
|
17
|
-
|
|
17
|
+
const options = {
|
|
18
18
|
username: process.env.DB_USER,
|
|
19
19
|
password: process.env.DB_PASSWORD,
|
|
20
20
|
database: process.env.DB_DATABASE,
|
|
21
21
|
host: process.env.DB_HOST,
|
|
22
22
|
port: process.env.DB_PORT,
|
|
23
23
|
dialect: process.env.DB_DIALECT || 'sqlite',
|
|
24
|
-
logging: process.env.DB_LOGGING === 'on' ?
|
|
24
|
+
logging: process.env.DB_LOGGING === 'on' ? customLogger : false,
|
|
25
25
|
storage:
|
|
26
26
|
process.env.DB_STORAGE && process.env.DB_STORAGE !== ':memory:'
|
|
27
27
|
? resolve(process.cwd(), process.env.DB_STORAGE)
|
|
@@ -33,7 +33,19 @@ export function getConfigByEnv() {
|
|
|
33
33
|
timezone: process.env.DB_TIMEZONE,
|
|
34
34
|
underscored: process.env.DB_UNDERSCORED === 'true',
|
|
35
35
|
schema: process.env.DB_SCHEMA !== 'public' ? process.env.DB_SCHEMA : undefined,
|
|
36
|
+
dialectOptions: {},
|
|
36
37
|
};
|
|
38
|
+
|
|
39
|
+
if (process.env.DB_DIALECT == 'postgres') {
|
|
40
|
+
options.dialectOptions['application_name'] = 'nocobase.main';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return options;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function customLogger(queryString, queryObject) {
|
|
47
|
+
console.log(queryString); // outputs a string
|
|
48
|
+
console.log(queryObject.bind); // outputs an array
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
export function mockDatabase(options: IDatabaseOptions = {}): MockDatabase {
|
package/src/model.ts
CHANGED
|
@@ -151,6 +151,10 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
static async sync(options) {
|
|
154
|
+
if (this.collection.isView()) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
154
158
|
const model = this as any;
|
|
155
159
|
|
|
156
160
|
const _schema = model._schema;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Op, Sequelize } from 'sequelize';
|
|
2
|
+
|
|
3
|
+
const mapVal = (values, db) =>
|
|
4
|
+
values.map((v) => {
|
|
5
|
+
const collection = db.getCollection(v);
|
|
6
|
+
return Sequelize.literal(`'${collection.tableNameAsString()}'::regclass`);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
$childIn(values, ctx: any) {
|
|
11
|
+
const db = ctx.db;
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
[Op.in]: mapVal(values, db),
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
$childNotIn(values, ctx: any) {
|
|
18
|
+
const db = ctx.db;
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
[Op.notIn]: mapVal(values, db),
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
} as Record<string, any>;
|
package/src/operators/date.ts
CHANGED
|
@@ -1,41 +1,125 @@
|
|
|
1
|
+
import { parseDate } from '@nocobase/utils';
|
|
1
2
|
import { Op } from 'sequelize';
|
|
2
|
-
import moment, { MomentInput } from 'moment';
|
|
3
|
-
function stringToDate(value: string): Date {
|
|
4
|
-
return moment(value).toDate();
|
|
5
|
-
}
|
|
6
3
|
|
|
7
|
-
function
|
|
8
|
-
return
|
|
4
|
+
function isDate(input) {
|
|
5
|
+
return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
|
|
9
6
|
}
|
|
10
7
|
|
|
8
|
+
const toDate = (date) => {
|
|
9
|
+
if (isDate(date)) {
|
|
10
|
+
return date;
|
|
11
|
+
}
|
|
12
|
+
return new Date(date);
|
|
13
|
+
};
|
|
14
|
+
|
|
11
15
|
export default {
|
|
12
|
-
$dateOn(value) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
16
|
+
$dateOn(value, ctx) {
|
|
17
|
+
const r = parseDate(value, {
|
|
18
|
+
timezone: ctx.db.options.timezone,
|
|
19
|
+
});
|
|
20
|
+
if (typeof r === 'string') {
|
|
21
|
+
return {
|
|
22
|
+
[Op.eq]: toDate(r),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(r)) {
|
|
26
|
+
return {
|
|
27
|
+
[Op.and]: [{ [Op.gte]: toDate(r[0]) }, { [Op.lt]: toDate(r[1]) }],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`Invalid Date ${JSON.stringify(value)}`);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
$dateNotOn(value, ctx) {
|
|
34
|
+
const r = parseDate(value, {
|
|
35
|
+
timezone: ctx.db.options.timezone,
|
|
36
|
+
});
|
|
37
|
+
if (typeof r === 'string') {
|
|
38
|
+
return {
|
|
39
|
+
[Op.ne]: toDate(r),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(r)) {
|
|
43
|
+
return {
|
|
44
|
+
[Op.or]: [{ [Op.lt]: toDate(r[0]) }, { [Op.gte]: toDate(r[1]) }],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
throw new Error(`Invalid Date ${JSON.stringify(value)}`);
|
|
16
48
|
},
|
|
17
49
|
|
|
18
|
-
$
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
};
|
|
50
|
+
$dateBefore(value, ctx) {
|
|
51
|
+
const r = parseDate(value, {
|
|
52
|
+
timezone: ctx.db.options.timezone,
|
|
53
|
+
});
|
|
54
|
+
if (typeof r === 'string') {
|
|
55
|
+
return {
|
|
56
|
+
[Op.lt]: toDate(r),
|
|
57
|
+
};
|
|
58
|
+
} else if (Array.isArray(r)) {
|
|
59
|
+
return {
|
|
60
|
+
[Op.lt]: toDate(r[0]),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
throw new Error(`Invalid Date ${JSON.stringify(value)}`);
|
|
22
64
|
},
|
|
23
65
|
|
|
24
|
-
$
|
|
25
|
-
|
|
66
|
+
$dateNotBefore(value, ctx) {
|
|
67
|
+
const r = parseDate(value, {
|
|
68
|
+
timezone: ctx.db.options.timezone,
|
|
69
|
+
});
|
|
70
|
+
if (typeof r === 'string') {
|
|
71
|
+
return {
|
|
72
|
+
[Op.gte]: toDate(r),
|
|
73
|
+
};
|
|
74
|
+
} else if (Array.isArray(r)) {
|
|
75
|
+
return {
|
|
76
|
+
[Op.gte]: toDate(r[0]),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
throw new Error(`Invalid Date ${JSON.stringify(value)}`);
|
|
26
80
|
},
|
|
27
81
|
|
|
28
|
-
$
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
82
|
+
$dateAfter(value, ctx) {
|
|
83
|
+
const r = parseDate(value, {
|
|
84
|
+
timezone: ctx.db.options.timezone,
|
|
85
|
+
});
|
|
86
|
+
if (typeof r === 'string') {
|
|
87
|
+
return {
|
|
88
|
+
[Op.gt]: toDate(r),
|
|
89
|
+
};
|
|
90
|
+
} else if (Array.isArray(r)) {
|
|
91
|
+
return {
|
|
92
|
+
[Op.gte]: toDate(r[1]),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
throw new Error(`Invalid Date ${JSON.stringify(value)}`);
|
|
32
96
|
},
|
|
33
97
|
|
|
34
|
-
$
|
|
35
|
-
|
|
98
|
+
$dateNotAfter(value, ctx) {
|
|
99
|
+
const r = parseDate(value, {
|
|
100
|
+
timezone: ctx.db.options.timezone,
|
|
101
|
+
});
|
|
102
|
+
if (typeof r === 'string') {
|
|
103
|
+
return {
|
|
104
|
+
[Op.lte]: toDate(r),
|
|
105
|
+
};
|
|
106
|
+
} else if (Array.isArray(r)) {
|
|
107
|
+
return {
|
|
108
|
+
[Op.lt]: toDate(r[1]),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
throw new Error(`Invalid Date ${JSON.stringify(value)}`);
|
|
36
112
|
},
|
|
37
113
|
|
|
38
|
-
$
|
|
39
|
-
|
|
114
|
+
$dateBetween(value, ctx) {
|
|
115
|
+
const r = parseDate(value, {
|
|
116
|
+
timezone: ctx.db.options.timezone,
|
|
117
|
+
});
|
|
118
|
+
if (r) {
|
|
119
|
+
return {
|
|
120
|
+
[Op.and]: [{ [Op.gte]: toDate(r[0]) }, { [Op.lt]: toDate(r[1]) }],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
throw new Error(`Invalid Date ${JSON.stringify(value)}`);
|
|
40
124
|
},
|
|
41
125
|
} as Record<string, any>;
|
package/src/operators/index.ts
CHANGED
|
@@ -4,7 +4,9 @@ export default {
|
|
|
4
4
|
...require('./array').default,
|
|
5
5
|
...require('./empty').default,
|
|
6
6
|
...require('./string').default,
|
|
7
|
+
...require('./eq').default,
|
|
7
8
|
...require('./ne').default,
|
|
8
9
|
...require('./notIn').default,
|
|
9
10
|
...require('./boolean').default,
|
|
11
|
+
...require('./child-collection').default,
|
|
10
12
|
};
|
package/src/operators/ne.ts
CHANGED
|
@@ -2,15 +2,20 @@ import { Op } from 'sequelize';
|
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
4
|
$ne(val, ctx) {
|
|
5
|
+
if (Array.isArray(val)) {
|
|
6
|
+
return {
|
|
7
|
+
[Op.notIn]: val,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
5
10
|
return val === null
|
|
6
11
|
? {
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
[Op.ne]: null,
|
|
13
|
+
}
|
|
9
14
|
: {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
[Op.or]: {
|
|
16
|
+
[Op.ne]: val,
|
|
17
|
+
[Op.is]: null,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
15
20
|
},
|
|
16
21
|
} as Record<string, any>;
|