mythix-orm 1.0.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/.eslintrc.js +94 -0
- package/.vscode/launch.json +34 -0
- package/.vscode/settings.json +10 -0
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/lib/connection/connection-base.js +877 -0
- package/lib/connection/index.js +11 -0
- package/lib/connection/literals/average-literal.js +14 -0
- package/lib/connection/literals/count-literal.js +18 -0
- package/lib/connection/literals/distinct-literal.js +14 -0
- package/lib/connection/literals/index.js +23 -0
- package/lib/connection/literals/literal-base.js +62 -0
- package/lib/connection/literals/literal-field-base.js +45 -0
- package/lib/connection/literals/literal.js +11 -0
- package/lib/connection/literals/max-literal.js +14 -0
- package/lib/connection/literals/min-literal.js +14 -0
- package/lib/connection/literals/sum-literal.js +14 -0
- package/lib/connection/query-generator-base.js +864 -0
- package/lib/errors/base-error.js +14 -0
- package/lib/errors/connection/access-denied-error.js +13 -0
- package/lib/errors/connection/connection-acquire-timeout-error.js +13 -0
- package/lib/errors/connection/connection-refused-error.js +13 -0
- package/lib/errors/connection/connection-timed-out-error.js +13 -0
- package/lib/errors/connection/host-not-found-error.js +13 -0
- package/lib/errors/connection/host-not-reachable-error.js +13 -0
- package/lib/errors/connection/index.js +19 -0
- package/lib/errors/connection/invalid-connection-error.js +13 -0
- package/lib/errors/connection-base-error.js +13 -0
- package/lib/errors/database/exclusion-constraint-error.js +13 -0
- package/lib/errors/database/foreign-key-constraint-error.js +13 -0
- package/lib/errors/database/index.js +13 -0
- package/lib/errors/database/timeout-error.js +13 -0
- package/lib/errors/database/unknown-constraint-error.js +13 -0
- package/lib/errors/database-base-error.js +17 -0
- package/lib/errors/index.js +44 -0
- package/lib/field.js +18 -0
- package/lib/index.js +43 -0
- package/lib/model.js +1374 -0
- package/lib/proxy-class/index.js +3 -0
- package/lib/proxy-class/proxy-class.js +269 -0
- package/lib/query-engine/field-scope.js +99 -0
- package/lib/query-engine/index.js +13 -0
- package/lib/query-engine/model-scope.js +198 -0
- package/lib/query-engine/query-engine-base.js +325 -0
- package/lib/query-engine/query-engine.js +212 -0
- package/lib/types/concrete/bigint-type.js +62 -0
- package/lib/types/concrete/blob-type.js +46 -0
- package/lib/types/concrete/boolean-type.js +41 -0
- package/lib/types/concrete/char-type.js +39 -0
- package/lib/types/concrete/date-type.js +87 -0
- package/lib/types/concrete/datetime-type.js +92 -0
- package/lib/types/concrete/float-type.js +47 -0
- package/lib/types/concrete/foreign-key-type.js +208 -0
- package/lib/types/concrete/index.js +53 -0
- package/lib/types/concrete/integer-type.js +51 -0
- package/lib/types/concrete/string-type.js +44 -0
- package/lib/types/concrete/text-type.js +44 -0
- package/lib/types/concrete/uuid-base.js +77 -0
- package/lib/types/concrete/uuid-v1-type.js +99 -0
- package/lib/types/concrete/uuid-v3-type.js +108 -0
- package/lib/types/concrete/uuid-v4-type.js +90 -0
- package/lib/types/concrete/uuid-v5-type.js +108 -0
- package/lib/types/concrete/xid-type.js +58 -0
- package/lib/types/helpers/default-helpers.js +127 -0
- package/lib/types/helpers/index.js +35 -0
- package/lib/types/index.js +94 -0
- package/lib/types/type.js +244 -0
- package/lib/types/virtual/index.js +14 -0
- package/lib/types/virtual/model-type.js +141 -0
- package/lib/types/virtual/models-type.js +264 -0
- package/lib/types/virtual/relational-type-base.js +323 -0
- package/lib/utils/index.js +65 -0
- package/lib/utils/misc-utils.js +73 -0
- package/lib/utils/model-utils.js +704 -0
- package/lib/utils/query-utils.js +186 -0
- package/package.json +63 -0
- package/playground/test01.js +15 -0
- package/spec/connection/connection-base-spec.js +126 -0
- package/spec/connection/literals/average-literal-spec.js +45 -0
- package/spec/connection/literals/count-literal-spec.js +42 -0
- package/spec/connection/literals/distinct-literal-spec.js +42 -0
- package/spec/connection/literals/literal-spec.js +26 -0
- package/spec/connection/literals/max-literal-spec.js +42 -0
- package/spec/connection/literals/min-literal-spec.js +42 -0
- package/spec/connection/literals/sum-literal-spec.js +42 -0
- package/spec/helpers/default-helpers-spec.js +108 -0
- package/spec/model-spec.js +736 -0
- package/spec/proxy-class/proxy-class-spec.js +173 -0
- package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_chain_query_conditions-001.snapshot +94 -0
- package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_construct_a_query_context_with_a_model_call-001.snapshot +35 -0
- package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_construct_a_query_context_with_a_model_sub-001.snapshot +35 -0
- package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_set_a_default_scope_on_a_model-001.snapshot +57 -0
- package/spec/query-engine/__snapshots__/QueryEngine-operations-query_operations_and_chaining-can_unscope_default_scope_on_a_model-001.snapshot +35 -0
- package/spec/query-engine/query-engine-spec.js +99 -0
- package/spec/support/jasmine.json +13 -0
- package/spec/support/models/blob-test-model.js +19 -0
- package/spec/support/models/extended-user-model.js +38 -0
- package/spec/support/models/index.js +27 -0
- package/spec/support/models/number-model.js +24 -0
- package/spec/support/models/role-model.js +26 -0
- package/spec/support/models/role-thing-model.js +41 -0
- package/spec/support/models/scoped-user-model.js +13 -0
- package/spec/support/models/time-model.js +36 -0
- package/spec/support/models/user-model.js +70 -0
- package/spec/support/models/user-role-model.js +36 -0
- package/spec/support/models/user-thing-model.js +46 -0
- package/spec/support/models/validation-test-model.js +40 -0
- package/spec/support/snapshots.js +293 -0
- package/spec/support/test-helpers.js +13 -0
- package/spec/types/concrete/bigint-type-spec.js +84 -0
- package/spec/types/concrete/boolean-type-spec.js +83 -0
- package/spec/types/concrete/date-type-spec.js +85 -0
- package/spec/types/concrete/datetime-type-spec.js +87 -0
- package/spec/types/concrete/float-type-spec.js +71 -0
- package/spec/types/concrete/foreign-key-type-spec.js +64 -0
- package/spec/types/concrete/integer-type-spec.js +71 -0
- package/spec/types/concrete/string-type-spec.js +91 -0
- package/spec/types/concrete/uuid-v1-type-spec.js +73 -0
- package/spec/types/concrete/uuid-v4-type-spec.js +65 -0
- package/spec/types/type-spec.js +101 -0
- package/spec/types/virtual/model-types-spec.js +401 -0
- package/spec/utils/misc-utils-spec.js +61 -0
- package/spec/utils/model-utils-spec.js +55 -0
- package/spec/utils/query-utils-spec.js +105 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Nife = require('nife');
|
|
4
|
+
const UUID = require('uuid');
|
|
5
|
+
|
|
6
|
+
function isUUID(value) {
|
|
7
|
+
return UUID.validate(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function sanitizeFieldString(str) {
|
|
11
|
+
return ('' + str).replace(/[^\w:.]+/g, '').replace(/([:.])+/g, '$1');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseQualifiedName(str) {
|
|
15
|
+
let fieldNames = [];
|
|
16
|
+
let modelName;
|
|
17
|
+
|
|
18
|
+
let parts = sanitizeFieldString(str).split(/:/).filter(Boolean);
|
|
19
|
+
if (parts.length > 1) {
|
|
20
|
+
modelName = parts[0];
|
|
21
|
+
|
|
22
|
+
let fieldPart = parts.slice(1).join('');
|
|
23
|
+
if (fieldPart !== '*')
|
|
24
|
+
fieldNames = fieldPart.split(/\.+/g).filter(Boolean);
|
|
25
|
+
} else {
|
|
26
|
+
if ((/^[A-Z]/).test('' + parts[0]))
|
|
27
|
+
return { modelName: parts[0], fieldNames: [] };
|
|
28
|
+
|
|
29
|
+
fieldNames = parts.join('').split(/\.+/g).filter(Boolean);
|
|
30
|
+
|
|
31
|
+
if (fieldNames.length === 1 && fieldNames[0].match(/^[A-Z]/)) {
|
|
32
|
+
modelName = fieldNames[0];
|
|
33
|
+
fieldNames = [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { modelName, fieldNames };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function injectModelMethod(self, method, methodName, fullMethodName) {
|
|
41
|
+
Object.defineProperties(self, {
|
|
42
|
+
[fullMethodName]: {
|
|
43
|
+
writable: true,
|
|
44
|
+
enumberable: false,
|
|
45
|
+
configurable: true,
|
|
46
|
+
value: method,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!(methodName in self)) {
|
|
51
|
+
Object.defineProperties(self, {
|
|
52
|
+
[methodName]: {
|
|
53
|
+
writable: true,
|
|
54
|
+
enumberable: false,
|
|
55
|
+
configurable: true,
|
|
56
|
+
value: function(...args) {
|
|
57
|
+
return method.apply(this, args);
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function fieldToFullyQualifiedName(field, Model) {
|
|
65
|
+
if (!field)
|
|
66
|
+
return;
|
|
67
|
+
|
|
68
|
+
if (field.Model && field.fieldName)
|
|
69
|
+
return `${field.Model.getModelName()}:${field.fieldName}`;
|
|
70
|
+
|
|
71
|
+
if (!Nife.instanceOf(field, 'string'))
|
|
72
|
+
return;
|
|
73
|
+
|
|
74
|
+
let def = parseQualifiedName(field);
|
|
75
|
+
if (!def.modelName && Model)
|
|
76
|
+
def.modelName = Model.getModelName();
|
|
77
|
+
|
|
78
|
+
if (Nife.isEmpty(def.modelName) || Nife.isEmpty(def.fieldNames))
|
|
79
|
+
return;
|
|
80
|
+
|
|
81
|
+
return `${def.modelName}:${def.fieldNames[0]}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function sortModelNamesByDependencyOrder(connection, modelNames, dependencyHelper) {
|
|
85
|
+
let modelOrderMap = {};
|
|
86
|
+
for (let i = 0, il = modelNames.length; i < il; i++) {
|
|
87
|
+
let modelName = modelNames[i];
|
|
88
|
+
let Model = connection.getModel(modelName);
|
|
89
|
+
let dependentModelNames = dependencyHelper(Model, modelName);
|
|
90
|
+
|
|
91
|
+
modelOrderMap[modelName] = dependentModelNames;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let sortedModelNames = modelNames.sort((a, b) => {
|
|
95
|
+
if (a === b)
|
|
96
|
+
return 0;
|
|
97
|
+
|
|
98
|
+
let orderA = modelOrderMap[a];
|
|
99
|
+
let orderB = modelOrderMap[b];
|
|
100
|
+
|
|
101
|
+
if (orderA.indexOf(b) >= 0)
|
|
102
|
+
return 1;
|
|
103
|
+
|
|
104
|
+
if (orderB.indexOf(a) >= 0)
|
|
105
|
+
return -1;
|
|
106
|
+
|
|
107
|
+
return Math.sign(orderA.length - orderB.length);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return sortedModelNames;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function sortModelNamesByCreationOrder(connection, modelNames) {
|
|
114
|
+
const getForeignKeyTargets = (Model) => {
|
|
115
|
+
let relatedModelNames = new Set();
|
|
116
|
+
|
|
117
|
+
Model.iterateFields(({ field }) => {
|
|
118
|
+
let fieldType = field.type;
|
|
119
|
+
if (!fieldType.isForeignKey())
|
|
120
|
+
return;
|
|
121
|
+
|
|
122
|
+
let TargetModel = fieldType.getTargetModel(connection);
|
|
123
|
+
if (!TargetModel)
|
|
124
|
+
return;
|
|
125
|
+
|
|
126
|
+
let targetModelName = TargetModel.getModelName();
|
|
127
|
+
relatedModelNames.add(targetModelName);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return Array.from(relatedModelNames.values());
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return sortModelNamesByDependencyOrder(connection, modelNames, getForeignKeyTargets);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function getRelationalModelStatusForField(connection, self, field, ...args) {
|
|
137
|
+
if (!field || !field.type.isRelational())
|
|
138
|
+
return { relationalMap: {} };
|
|
139
|
+
|
|
140
|
+
let PrimaryModel = self.getModel();
|
|
141
|
+
let primaryModelName = self.getModelName();
|
|
142
|
+
let relationalMap = {
|
|
143
|
+
[primaryModelName]: {
|
|
144
|
+
create: !self.isPersisted(),
|
|
145
|
+
instance: self,
|
|
146
|
+
modelName: primaryModelName,
|
|
147
|
+
Model: PrimaryModel,
|
|
148
|
+
fields: new Map(),
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const hasValidPrimaryKey = (model) => {
|
|
153
|
+
if (!model)
|
|
154
|
+
return false;
|
|
155
|
+
|
|
156
|
+
if (typeof model.hasValidPrimaryKey === 'function')
|
|
157
|
+
return model.hasValidPrimaryKey();
|
|
158
|
+
|
|
159
|
+
return false;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const addModelStatus = (relation, opposingRelation, swapFieldOrder, _status) => {
|
|
163
|
+
if (relation.field && relation.field.type.isRelational() && relation.field.type.isManyRelation())
|
|
164
|
+
return;
|
|
165
|
+
|
|
166
|
+
if (opposingRelation.field && opposingRelation.field.type.isRelational() && opposingRelation.field.type.isManyRelation())
|
|
167
|
+
return;
|
|
168
|
+
|
|
169
|
+
let status = _status || {};
|
|
170
|
+
let relationFieldName = (relation.fieldName) ? `:${relation.fieldName}` : '';
|
|
171
|
+
let relationFQName = `${relation.modelName}${relationFieldName}`;
|
|
172
|
+
let opposingRelationFieldName = (opposingRelation.fieldName) ? `:${opposingRelation.fieldName}` : '';
|
|
173
|
+
let opposingRelationFQName = `${opposingRelation.modelName}${opposingRelationFieldName}`;
|
|
174
|
+
let fields;
|
|
175
|
+
|
|
176
|
+
let modelRelation = relationalMap[relation.modelName];
|
|
177
|
+
if (!modelRelation) {
|
|
178
|
+
fields = new Map();
|
|
179
|
+
relationalMap[relation.modelName] = Object.assign(status, { create: !status.instance, Model: relation.Model, modelName: relation.modelName }, { fields });
|
|
180
|
+
} else {
|
|
181
|
+
let instance = modelRelation.instance || status.instance;
|
|
182
|
+
|
|
183
|
+
fields = modelRelation.fields || new Map();
|
|
184
|
+
relationalMap[relation.modelName] = Object.assign(relationalMap[relation.modelName], status, { create: !instance, instance, fields });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// this key/value pair must be flipped so the
|
|
188
|
+
// parent faces the child. This is so that the field
|
|
189
|
+
// set can hold many model types and fields.
|
|
190
|
+
fields.set((swapFieldOrder) ? relationFQName : opposingRelationFQName, (swapFieldOrder) ? opposingRelationFQName : relationFQName);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const findAvailableRelationalModel = (modelName, field) => {
|
|
194
|
+
if (modelName === primaryModelName)
|
|
195
|
+
return self;
|
|
196
|
+
|
|
197
|
+
if (!field)
|
|
198
|
+
return;
|
|
199
|
+
|
|
200
|
+
if (!field.type.isRelational())
|
|
201
|
+
return;
|
|
202
|
+
|
|
203
|
+
// Don't attempt multi-relational fields
|
|
204
|
+
if (field.type.isManyRelation())
|
|
205
|
+
return;
|
|
206
|
+
|
|
207
|
+
let relation = relationalMap[modelName];
|
|
208
|
+
let instance = (relation) ? relation.instance : null;
|
|
209
|
+
if (!instance)
|
|
210
|
+
return;
|
|
211
|
+
|
|
212
|
+
let value = instance[field.fieldName];
|
|
213
|
+
if (Array.isArray(value))
|
|
214
|
+
return; // we need to create
|
|
215
|
+
|
|
216
|
+
return value;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const addRelationalStatus = async (source, target) => {
|
|
220
|
+
if (field.type.isManyRelation()) {
|
|
221
|
+
// we need to create
|
|
222
|
+
addModelStatus(source, target);
|
|
223
|
+
addModelStatus(target, source);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let model = findAvailableRelationalModel(source.modelName, source.field);
|
|
228
|
+
if (!hasValidPrimaryKey(model))
|
|
229
|
+
addModelStatus(source, target, { instance: model });
|
|
230
|
+
else
|
|
231
|
+
addModelStatus(source, target, { instance: model });
|
|
232
|
+
|
|
233
|
+
model = findAvailableRelationalModel(target.modelName, target.field);
|
|
234
|
+
if (!hasValidPrimaryKey(model))
|
|
235
|
+
addModelStatus(target, source, { instance: model });
|
|
236
|
+
else
|
|
237
|
+
addModelStatus(target, source, { instance: model });
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
let TargetModel;
|
|
241
|
+
let TargetField;
|
|
242
|
+
|
|
243
|
+
await field.type.walkQueryRelations(connection, async ({ TargetModel: _TargetModel, TargetField: _TargetField, source, target }) => {
|
|
244
|
+
if (!TargetModel)
|
|
245
|
+
TargetModel = _TargetModel;
|
|
246
|
+
|
|
247
|
+
if (!TargetField)
|
|
248
|
+
TargetField = _TargetField;
|
|
249
|
+
|
|
250
|
+
await addRelationalStatus(source, target);
|
|
251
|
+
}, { self, field: field }, ...args);
|
|
252
|
+
|
|
253
|
+
return { relationalMap, TargetModel, TargetField };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function constructModelsForCreationFromOriginField(connection, self, field, attributes) {
|
|
257
|
+
if (!field)
|
|
258
|
+
return;
|
|
259
|
+
|
|
260
|
+
if (!field.type.isRelational())
|
|
261
|
+
return;
|
|
262
|
+
|
|
263
|
+
const getRelationalModel = (relationalMap, modelName) => {
|
|
264
|
+
let status = relationalMap[modelName];
|
|
265
|
+
return (status) ? status.instance : null;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
let {
|
|
269
|
+
relationalMap,
|
|
270
|
+
TargetModel,
|
|
271
|
+
} = await getRelationalModelStatusForField(connection, self, field);
|
|
272
|
+
|
|
273
|
+
let targetModelName = TargetModel.getModelName();
|
|
274
|
+
let sortedModelNames = sortModelNamesByCreationOrder(connection, Array.from(Object.keys(relationalMap)));
|
|
275
|
+
|
|
276
|
+
for (let i = 0, il = sortedModelNames.length; i < il; i++) {
|
|
277
|
+
let modelName = sortedModelNames[i];
|
|
278
|
+
let relationStatus = relationalMap[modelName];
|
|
279
|
+
let RelatedModel = relationStatus.Model;
|
|
280
|
+
let thisModelInstance = relationStatus.instance;
|
|
281
|
+
|
|
282
|
+
if (!(thisModelInstance instanceof RelatedModel)) {
|
|
283
|
+
let modelAttributes = relationStatus.instance;
|
|
284
|
+
if (modelName === targetModelName)
|
|
285
|
+
modelAttributes = attributes;
|
|
286
|
+
|
|
287
|
+
// Is this a persisted model?
|
|
288
|
+
if (modelAttributes instanceof RelatedModel && modelAttributes.isPersisted())
|
|
289
|
+
thisModelInstance = modelAttributes;
|
|
290
|
+
else
|
|
291
|
+
thisModelInstance = new RelatedModel(modelAttributes, { connection });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
setRelationalValues(connection, TargetModel, thisModelInstance, self.getModel(), self);
|
|
295
|
+
relationStatus.create = !thisModelInstance.isPersisted();
|
|
296
|
+
relationStatus.instance = thisModelInstance;
|
|
297
|
+
|
|
298
|
+
for (let [ sourceFQField, targetFQField ] of relationStatus.fields.entries()) {
|
|
299
|
+
let targetDef = parseQualifiedName(targetFQField);
|
|
300
|
+
if (Nife.isEmpty(targetDef.fieldNames))
|
|
301
|
+
continue;
|
|
302
|
+
|
|
303
|
+
let targetFieldName = targetDef.fieldNames[0];
|
|
304
|
+
let TargetField = connection.getField(targetFieldName, targetDef.modelName);
|
|
305
|
+
|
|
306
|
+
// We never assign to the primaryID
|
|
307
|
+
if (TargetField.primaryKey)
|
|
308
|
+
continue;
|
|
309
|
+
|
|
310
|
+
let sourceDef = parseQualifiedName(sourceFQField);
|
|
311
|
+
let relatedModelInstance = (modelName === sourceDef.modelName) ? thisModelInstance : getRelationalModel(relationalMap, sourceDef.modelName);
|
|
312
|
+
if (!relatedModelInstance)
|
|
313
|
+
continue;
|
|
314
|
+
|
|
315
|
+
// If target field is relational then that
|
|
316
|
+
// means we want to assign the entire model.
|
|
317
|
+
if (TargetField.type.isRelational()) {
|
|
318
|
+
// We don't play around with many relational fields
|
|
319
|
+
if (TargetField.type.isManyRelation())
|
|
320
|
+
continue;
|
|
321
|
+
|
|
322
|
+
thisModelInstance[targetDef.fieldNames[0]] = relatedModelInstance;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
thisModelInstance[targetDef.fieldNames[0]] = relatedModelInstance[sourceDef.fieldNames[0]];
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { relationalMap, sortedModelNames };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function createAndSaveAllRelatedModels(connection, self, field, allModelAttributes, options) {
|
|
334
|
+
if (!self)
|
|
335
|
+
return;
|
|
336
|
+
|
|
337
|
+
if (!self.isPersisted())
|
|
338
|
+
throw new Error('ModelUtils::createAndSaveAllRelatedModels: Parent model must be persisted before you attempt to save related child models.');
|
|
339
|
+
|
|
340
|
+
// Find related models that need to be created
|
|
341
|
+
let TargetModel = field.type.getTargetModel(connection);
|
|
342
|
+
let targetModelName = TargetModel.getModelName();
|
|
343
|
+
let parentModelName = self.getModelName();
|
|
344
|
+
let storedModelMap = new Map();
|
|
345
|
+
let parentModelSet = new Set();
|
|
346
|
+
let relatedInfos = [];
|
|
347
|
+
|
|
348
|
+
parentModelSet.add(self);
|
|
349
|
+
storedModelMap.set(parentModelName, parentModelSet);
|
|
350
|
+
|
|
351
|
+
for (let i = 0, il = allModelAttributes.length; i < il; i++) {
|
|
352
|
+
let modelAttributes = allModelAttributes[i];
|
|
353
|
+
if (!modelAttributes)
|
|
354
|
+
continue;
|
|
355
|
+
|
|
356
|
+
let result = await constructModelsForCreationFromOriginField(connection, self, field, modelAttributes);
|
|
357
|
+
relatedInfos.push(result);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Create a map of all model instances by type
|
|
361
|
+
// so that we can bulk insert models
|
|
362
|
+
let fullModelMap = new Map();
|
|
363
|
+
let relationalModelMap = new Map();
|
|
364
|
+
let persistedModelMap = new Map();
|
|
365
|
+
let index = 0;
|
|
366
|
+
|
|
367
|
+
for (let i = 0, il = relatedInfos.length; i < il; i++) {
|
|
368
|
+
let info = relatedInfos[i];
|
|
369
|
+
let { relationalMap, sortedModelNames } = info;
|
|
370
|
+
|
|
371
|
+
for (let j = 0, jl = sortedModelNames.length; j < jl; j++) {
|
|
372
|
+
let modelName = sortedModelNames[j];
|
|
373
|
+
let modelSet = fullModelMap.get(modelName);
|
|
374
|
+
if (!modelSet) {
|
|
375
|
+
modelSet = new Set();
|
|
376
|
+
fullModelMap.set(modelName, modelSet);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
let thisStatus = relationalMap[modelName];
|
|
380
|
+
let thisModelInstance = thisStatus.instance;
|
|
381
|
+
|
|
382
|
+
if (!thisModelInstance.isPersisted()) {
|
|
383
|
+
thisModelInstance.__order = index;
|
|
384
|
+
modelSet.add(thisModelInstance);
|
|
385
|
+
} else {
|
|
386
|
+
let storedSet = storedModelMap.get(modelName);
|
|
387
|
+
if (!storedSet) {
|
|
388
|
+
storedSet = new Set();
|
|
389
|
+
storedModelMap.set(modelName, storedSet);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
thisModelInstance.__order = index;
|
|
393
|
+
storedSet.add(thisModelInstance);
|
|
394
|
+
|
|
395
|
+
let persistedSet = persistedModelMap.get(modelName);
|
|
396
|
+
if (!persistedSet) {
|
|
397
|
+
persistedSet = new Set();
|
|
398
|
+
persistedModelMap.set(modelName, persistedSet);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
persistedSet.add(thisModelInstance);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
index++;
|
|
405
|
+
|
|
406
|
+
relationalModelMap.set(thisModelInstance, info);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const updateRelatedModelAttributes = (SetModel, modelName, models, assignRelated) => {
|
|
411
|
+
// Iterate all models and update their
|
|
412
|
+
// related model attributes
|
|
413
|
+
for (let model of models) {
|
|
414
|
+
let relationInfo = relationalModelMap.get(model);
|
|
415
|
+
let { relationalMap, sortedModelNames } = relationInfo;
|
|
416
|
+
|
|
417
|
+
// Assign related attributes from stored models
|
|
418
|
+
for (let k = 0, kl = sortedModelNames.length; k < kl; k++) {
|
|
419
|
+
let relatedModelName = sortedModelNames[k];
|
|
420
|
+
|
|
421
|
+
// Model can't be related to itself
|
|
422
|
+
if (relatedModelName === modelName)
|
|
423
|
+
continue;
|
|
424
|
+
|
|
425
|
+
let relationStatus = relationalMap[relatedModelName];
|
|
426
|
+
let SourceModel = relationStatus.Model;
|
|
427
|
+
let sourceModelInstance = relationStatus.instance;
|
|
428
|
+
|
|
429
|
+
if (assignRelated)
|
|
430
|
+
assignRelatedModels(model, [ sourceModelInstance ]);
|
|
431
|
+
|
|
432
|
+
// Is this model related to the set model?
|
|
433
|
+
// If not, simply continue
|
|
434
|
+
if (!SetModel.isForeignKeyTargetModel(connection, relatedModelName))
|
|
435
|
+
continue;
|
|
436
|
+
|
|
437
|
+
setRelationalValues(connection, SetModel, model, SourceModel, sourceModelInstance);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// Create each model and all related models
|
|
443
|
+
index = 0;
|
|
444
|
+
for (let [ modelName, models ] of fullModelMap) {
|
|
445
|
+
let SetModel = connection.getModel(modelName);
|
|
446
|
+
|
|
447
|
+
if (index > 0) {
|
|
448
|
+
// Now assign related attributes from all other
|
|
449
|
+
// related models before we save
|
|
450
|
+
updateRelatedModelAttributes(SetModel, modelName, models);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
index++;
|
|
454
|
+
|
|
455
|
+
if (models.size === 0)
|
|
456
|
+
continue;
|
|
457
|
+
|
|
458
|
+
// Create models
|
|
459
|
+
let storedModels = await connection.insert(SetModel, models, options);
|
|
460
|
+
let storedSet = storedModelMap.get(modelName);
|
|
461
|
+
if (!storedSet) {
|
|
462
|
+
storedSet = new Set();
|
|
463
|
+
storedModelMap.set(modelName, storedSet);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
for (let j = 0, jl = storedModels.length; j < jl; j++) {
|
|
467
|
+
let storedModel = storedModels[j];
|
|
468
|
+
storedSet.add(storedModel);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Now update all related fields for all stored models
|
|
473
|
+
for (let [ modelName, models ] of fullModelMap) {
|
|
474
|
+
let SetModel = connection.getModel(modelName);
|
|
475
|
+
|
|
476
|
+
// Now assign related attributes from all other
|
|
477
|
+
// related models before we save
|
|
478
|
+
updateRelatedModelAttributes(SetModel, modelName, models, true);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Now update all related fields for all pre-persisted models
|
|
482
|
+
// Models that were already persisted before the operation
|
|
483
|
+
// are split apart and treated separately
|
|
484
|
+
for (let [ modelName, models ] of persistedModelMap) {
|
|
485
|
+
let SetModel = connection.getModel(modelName);
|
|
486
|
+
|
|
487
|
+
// Now assign related attributes from all other
|
|
488
|
+
// related models before we save
|
|
489
|
+
updateRelatedModelAttributes(SetModel, modelName, models, true);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Finally return created models
|
|
493
|
+
// Sorting final results by provided model
|
|
494
|
+
// order is important, since we split out any
|
|
495
|
+
// pre-persisted models and treat them separately
|
|
496
|
+
let finalModels = Array.from(storedModelMap.get(targetModelName).values()).sort((a, b) => {
|
|
497
|
+
let x = a.__order;
|
|
498
|
+
let y = b.__order;
|
|
499
|
+
|
|
500
|
+
if (x === y)
|
|
501
|
+
return 0;
|
|
502
|
+
|
|
503
|
+
return (x < y) ? -1 : 1;
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
return finalModels;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function setRelationalValues(connection, TargetModel, targetModelInstance, SourceModel, sourceModelInstance) {
|
|
510
|
+
let targetModelName = TargetModel.getModelName();
|
|
511
|
+
let sourceModelName = SourceModel.getModelName();
|
|
512
|
+
|
|
513
|
+
if (targetModelName === sourceModelName)
|
|
514
|
+
return targetModelInstance;
|
|
515
|
+
|
|
516
|
+
let fieldSets = TargetModel.getForeignKeysTargetFieldNames(connection, sourceModelName);
|
|
517
|
+
|
|
518
|
+
// Update fields to related model
|
|
519
|
+
for (let i = 0, il = fieldSets.length; i < il; i++) {
|
|
520
|
+
let fieldSet = fieldSets[i];
|
|
521
|
+
|
|
522
|
+
// This might be confusing... the right hand side is
|
|
523
|
+
// pulling from the "source" table in the relationship
|
|
524
|
+
// (aka the "child" relationship), whereas we are pulling
|
|
525
|
+
// from the "target" or "parent" relationship on the left.
|
|
526
|
+
// Source table -> target model (copy destination).
|
|
527
|
+
let targetFieldName = fieldSet.sourceFieldName;
|
|
528
|
+
let sourceFieldName = fieldSet.targetFieldName;
|
|
529
|
+
let sourceModelValue = (sourceModelInstance) ? sourceModelInstance[sourceFieldName] : null;
|
|
530
|
+
|
|
531
|
+
targetModelInstance[targetFieldName] = sourceModelValue;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return targetModelInstance;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function assignRelatedModels(model, _relatedModels) {
|
|
538
|
+
if (!model || !_relatedModels)
|
|
539
|
+
return;
|
|
540
|
+
|
|
541
|
+
let relatedModels = Nife.toArray(_relatedModels);
|
|
542
|
+
for (let i = 0, il = relatedModels.length; i < il; i++) {
|
|
543
|
+
let relatedModel = relatedModels[i];
|
|
544
|
+
let modelName = relatedModel.getModelName();
|
|
545
|
+
let pluralName = relatedModel.getPluralModelName();
|
|
546
|
+
let relatedScope = model[pluralName];
|
|
547
|
+
|
|
548
|
+
if (!relatedScope) {
|
|
549
|
+
relatedScope = model[pluralName] = [];
|
|
550
|
+
|
|
551
|
+
let RelatedModel = relatedModel.getModel();
|
|
552
|
+
|
|
553
|
+
model.__assignedRelatedModels.set(modelName, RelatedModel);
|
|
554
|
+
|
|
555
|
+
Object.defineProperty(relatedScope, 'Model', {
|
|
556
|
+
writable: true,
|
|
557
|
+
enumberable: false,
|
|
558
|
+
configurable: true,
|
|
559
|
+
value: RelatedModel,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (relatedScope.indexOf(relatedModel) < 0) {
|
|
564
|
+
relatedScope.push(relatedModel);
|
|
565
|
+
|
|
566
|
+
let pkFieldName = relatedModel.getPrimaryKeyFieldName();
|
|
567
|
+
let pkValue = (pkFieldName) ? relatedModel[pkFieldName] : null;
|
|
568
|
+
|
|
569
|
+
if (Nife.isNotEmpty(pkValue)) {
|
|
570
|
+
Object.defineProperty(relatedScope, pkValue, {
|
|
571
|
+
writable: true,
|
|
572
|
+
enumberable: false,
|
|
573
|
+
configurable: true,
|
|
574
|
+
value: relatedModel,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function getPrimaryKeysForModels(connection, Model, _models, _options) {
|
|
582
|
+
let pkFieldName = Model.getPrimaryKeyFieldName();
|
|
583
|
+
if (!pkFieldName)
|
|
584
|
+
return [];
|
|
585
|
+
|
|
586
|
+
let models = Nife.toArray(_models).filter(Boolean);
|
|
587
|
+
let primaryIDs = models.map((model) => model[pkFieldName]).filter((id) => (id != null));
|
|
588
|
+
|
|
589
|
+
let options = _options || {};
|
|
590
|
+
if (!options.includeRelations)
|
|
591
|
+
return primaryIDs;
|
|
592
|
+
|
|
593
|
+
let primaryModelName = Model.getModelName();
|
|
594
|
+
let idMap = { [primaryModelName]: primaryIDs };
|
|
595
|
+
let providedRelationNames = (Array.isArray(options.includeRelations)) ? (options.includeRelations.filter(Boolean)) : null;
|
|
596
|
+
let skipRelations = options.skipRelations;
|
|
597
|
+
|
|
598
|
+
for (let i = 0, il = models.length; i < il; i++) {
|
|
599
|
+
let model = models[i];
|
|
600
|
+
if (!model)
|
|
601
|
+
continue;
|
|
602
|
+
|
|
603
|
+
let relatedModels = model.__assignedRelatedModels;
|
|
604
|
+
for (let [ relationModelName, RelatedModel ] of relatedModels) {
|
|
605
|
+
if (relationModelName === primaryModelName)
|
|
606
|
+
continue;
|
|
607
|
+
|
|
608
|
+
if (providedRelationNames && providedRelationNames.indexOf(relationModelName) < 0)
|
|
609
|
+
continue;
|
|
610
|
+
|
|
611
|
+
if (skipRelations && skipRelations.indexOf(relationModelName) >= 0)
|
|
612
|
+
continue;
|
|
613
|
+
|
|
614
|
+
let pluralModelName = RelatedModel.getPluralModelName();
|
|
615
|
+
let relatedModels = model[pluralModelName];
|
|
616
|
+
if (Nife.isEmpty(relatedModels))
|
|
617
|
+
continue;
|
|
618
|
+
|
|
619
|
+
let relationPKFieldName = RelatedModel.getPrimaryKeyFieldName();
|
|
620
|
+
if (!relationPKFieldName)
|
|
621
|
+
continue;
|
|
622
|
+
|
|
623
|
+
let idScope = idMap[relationModelName];
|
|
624
|
+
if (!idScope)
|
|
625
|
+
idScope = idMap[relationModelName] = [];
|
|
626
|
+
|
|
627
|
+
for (let k = 0, kl = relatedModels.length; k < kl; k++) {
|
|
628
|
+
let relatedModel = relatedModels[k];
|
|
629
|
+
let id = relatedModel[relationPKFieldName];
|
|
630
|
+
|
|
631
|
+
if (!id)
|
|
632
|
+
continue;
|
|
633
|
+
|
|
634
|
+
if (idScope.indexOf(id) >= 0)
|
|
635
|
+
continue;
|
|
636
|
+
|
|
637
|
+
idScope.push(id);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return idMap;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function buildQueryFromModelsAttributes(connection, Model, _models) {
|
|
646
|
+
let models = Nife.toArray(_models);
|
|
647
|
+
if (Nife.isEmpty(models))
|
|
648
|
+
return;
|
|
649
|
+
|
|
650
|
+
let isValidQuery = false;
|
|
651
|
+
let query = Model.where(connection);
|
|
652
|
+
let concreteFieldNames = [];
|
|
653
|
+
|
|
654
|
+
Model.iterateFields(({ fieldName, field }) => {
|
|
655
|
+
if (field.type.isVirtual())
|
|
656
|
+
return;
|
|
657
|
+
|
|
658
|
+
concreteFieldNames.push(fieldName);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
if (concreteFieldNames.length === 0)
|
|
662
|
+
return;
|
|
663
|
+
|
|
664
|
+
for (let i = 0, il = models.length; i < il; i++) {
|
|
665
|
+
let model = models[i];
|
|
666
|
+
if (!model)
|
|
667
|
+
continue;
|
|
668
|
+
|
|
669
|
+
let subQuery = Model.where(connection);
|
|
670
|
+
for (let j = 0, jl = concreteFieldNames.length; j < jl; j++) {
|
|
671
|
+
let fieldName = concreteFieldNames[j];
|
|
672
|
+
let value = model[fieldName];
|
|
673
|
+
if (value == null)
|
|
674
|
+
continue;
|
|
675
|
+
|
|
676
|
+
isValidQuery = true;
|
|
677
|
+
subQuery = subQuery.AND[fieldName].EQ(value);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
query = query.AND(subQuery);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (!isValidQuery)
|
|
684
|
+
return;
|
|
685
|
+
|
|
686
|
+
return query;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
module.exports = {
|
|
690
|
+
assignRelatedModels,
|
|
691
|
+
buildQueryFromModelsAttributes,
|
|
692
|
+
constructModelsForCreationFromOriginField,
|
|
693
|
+
createAndSaveAllRelatedModels,
|
|
694
|
+
fieldToFullyQualifiedName,
|
|
695
|
+
getPrimaryKeysForModels,
|
|
696
|
+
getRelationalModelStatusForField,
|
|
697
|
+
injectModelMethod,
|
|
698
|
+
isUUID,
|
|
699
|
+
parseQualifiedName,
|
|
700
|
+
sanitizeFieldString,
|
|
701
|
+
setRelationalValues,
|
|
702
|
+
sortModelNamesByCreationOrder,
|
|
703
|
+
sortModelNamesByDependencyOrder,
|
|
704
|
+
};
|