objection 3.1.4 → 3.2.0
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/README.md +3 -3
- package/lib/model/AjvValidator.js +60 -25
- package/lib/model/Model.js +3 -4
- package/lib/model/graph/ModelGraphNode.js +12 -0
- package/lib/model/modelToJson.js +2 -1
- package/lib/queryBuilder/QueryBuilder.js +156 -45
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +1 -1
- package/lib/queryBuilder/RelationExpression.js +8 -8
- package/lib/queryBuilder/graph/GraphFetcher.js +3 -1
- package/lib/queryBuilder/graph/GraphOptions.js +21 -2
- package/lib/queryBuilder/graph/GraphUpsert.js +30 -0
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +20 -14
- package/lib/queryBuilder/graph/insert/GraphInsert.js +6 -0
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +2 -0
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +15 -3
- package/lib/queryBuilder/join/RelationJoiner.js +17 -0
- package/lib/queryBuilder/operations/DelegateOperation.js +4 -0
- package/lib/queryBuilder/operations/NoneOperation.js +37 -0
- package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +5 -0
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +20 -0
- package/lib/queryBuilder/operations/eager/EagerOperation.js +3 -5
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +8 -2
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +15 -3
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +8 -2
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +9 -0
- package/lib/relations/RelationProperty.js +7 -3
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +1 -0
- package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +5 -0
- package/lib/relations/hasMany/HasManyRelateOperation.js +1 -0
- package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +6 -0
- package/lib/utils/identifierMapping.js +8 -2
- package/lib/utils/objectUtils.js +9 -5
- package/package.json +17 -13
- package/typings/objection/index.d.ts +77 -39
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[](https://stand-with-ukraine.pp.ua)
|
|
2
2
|
|
|
3
|
-
[](https://github.com/ditojs/objection/actions)
|
|
4
4
|
[](https://gitter.im/Vincit/objection.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
5
5
|
|
|
6
6
|
# [Objection.js](https://vincit.github.io/objection.js)
|
|
@@ -9,7 +9,7 @@ Objection.js is an [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping
|
|
|
9
9
|
|
|
10
10
|
Even though ORM is the best commonly known acronym to describe objection, a more accurate description is to call it **a relational query builder**. You get all the benefits of an SQL query builder but also a powerful set of tools for working with relations.
|
|
11
11
|
|
|
12
|
-
Objection.js is built on an SQL query builder called [knex](http://knexjs.org). All databases supported by knex are supported by objection.js. **SQLite3**, **Postgres** and **MySQL** are [thoroughly tested](https://github.com/
|
|
12
|
+
Objection.js is built on an SQL query builder called [knex](http://knexjs.org). All databases supported by knex are supported by objection.js. **SQLite3**, **Postgres** and **MySQL** are [thoroughly tested](https://github.com/ditojs/objection/actions).
|
|
13
13
|
|
|
14
14
|
What objection.js gives you:
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ What objection.js gives you:
|
|
|
17
17
|
- **Simple and fun way to [fetch, insert, update and delete](https://vincit.github.io/objection.js/guide/query-examples.html) objects using the full power of SQL**
|
|
18
18
|
- **Powerful mechanisms for [eager loading](https://vincit.github.io/objection.js/guide/query-examples.html#eager-loading), [inserting](https://vincit.github.io/objection.js/guide/query-examples.html#graph-inserts) and [upserting](https://vincit.github.io/objection.js/guide/query-examples.html#graph-upserts) object graphs**
|
|
19
19
|
- **Easy to use [transactions](https://vincit.github.io/objection.js/guide/transactions.html)**
|
|
20
|
-
- **Official [TypeScript](https://github.com/
|
|
20
|
+
- **Official [TypeScript](https://github.com/ditojs/objection/blob/main/typings/objection/index.d.ts) support**
|
|
21
21
|
- **Optional [JSON schema](https://vincit.github.io/objection.js/guide/validation.html) validation**
|
|
22
22
|
- **A way to [store complex documents](https://vincit.github.io/objection.js/guide/documents.html) as single rows**
|
|
23
23
|
|
|
@@ -35,9 +35,14 @@ class AjvValidator extends Validator {
|
|
|
35
35
|
// A cache for the compiled validator functions.
|
|
36
36
|
self.cache = new Map();
|
|
37
37
|
|
|
38
|
+
// The cache keys of the unmodified model class schemas.
|
|
39
|
+
self.modelCacheKeys = new WeakMap();
|
|
40
|
+
|
|
38
41
|
const setupAjv = (ajv) => {
|
|
39
|
-
conf.onCreateAjv
|
|
40
|
-
|
|
42
|
+
if (conf.onCreateAjv) {
|
|
43
|
+
conf.onCreateAjv(ajv);
|
|
44
|
+
}
|
|
45
|
+
// Only add Ajv formats if they weren't added in user-space already
|
|
41
46
|
if (!ajv.formats['date-time']) {
|
|
42
47
|
addFormats(ajv);
|
|
43
48
|
}
|
|
@@ -77,7 +82,7 @@ class AjvValidator extends Validator {
|
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
validator.call(model, json);
|
|
80
|
-
const error = parseValidationError(validator.errors, modelClass, options);
|
|
85
|
+
const error = parseValidationError(validator.errors, modelClass, options, this.ajvOptions);
|
|
81
86
|
|
|
82
87
|
if (error) {
|
|
83
88
|
throw error;
|
|
@@ -87,15 +92,26 @@ class AjvValidator extends Validator {
|
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
getValidator(modelClass, jsonSchema, isPatchObject) {
|
|
90
|
-
// Use the
|
|
95
|
+
// Use the Ajv custom serializer if provided.
|
|
91
96
|
const createCacheKey = this.ajvOptions.serialize || JSON.stringify;
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
jsonSchema
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
let cacheKey;
|
|
99
|
+
|
|
100
|
+
if (jsonSchema === modelClass.getJsonSchema()) {
|
|
101
|
+
// Optimization for the common case where jsonSchema is never modified.
|
|
102
|
+
// The key is only computed once per model class. It includes the schema
|
|
103
|
+
// contents so that model classes with the same `uniqueTag()` but
|
|
104
|
+
// different schemas don't share validators, while bound model classes
|
|
105
|
+
// with equal schemas still do.
|
|
106
|
+
cacheKey = this.modelCacheKeys.get(modelClass);
|
|
107
|
+
|
|
108
|
+
if (cacheKey === undefined) {
|
|
109
|
+
cacheKey = `${modelClass.uniqueTag()}:${createCacheKey(jsonSchema)}`;
|
|
110
|
+
this.modelCacheKeys.set(modelClass, cacheKey);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
cacheKey = createCacheKey(jsonSchema);
|
|
114
|
+
}
|
|
99
115
|
|
|
100
116
|
let validators = this.cache.get(cacheKey);
|
|
101
117
|
let validator = null;
|
|
@@ -103,7 +119,7 @@ class AjvValidator extends Validator {
|
|
|
103
119
|
if (!validators) {
|
|
104
120
|
validators = {
|
|
105
121
|
// Validator created for the schema object without `required` properties
|
|
106
|
-
// using the
|
|
122
|
+
// using the Ajv instance that doesn't set default values.
|
|
107
123
|
patchValidator: null,
|
|
108
124
|
|
|
109
125
|
// Validator created for the unmodified schema.
|
|
@@ -143,7 +159,7 @@ class AjvValidator extends Validator {
|
|
|
143
159
|
}
|
|
144
160
|
}
|
|
145
161
|
|
|
146
|
-
function parseValidationError(errors, modelClass, options) {
|
|
162
|
+
function parseValidationError(errors, modelClass, options, ajvOptions) {
|
|
147
163
|
if (!errors) {
|
|
148
164
|
return null;
|
|
149
165
|
}
|
|
@@ -178,13 +194,21 @@ function parseValidationError(errors, modelClass, options) {
|
|
|
178
194
|
// More than one error can occur for the same key in Ajv, merge them in the array:
|
|
179
195
|
const array = errorHash[key] || (errorHash[key] = []);
|
|
180
196
|
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
array.unshift({
|
|
197
|
+
// Prepare error object
|
|
198
|
+
const errorObj = {
|
|
184
199
|
message: error.message,
|
|
185
200
|
keyword: error.keyword,
|
|
186
201
|
params: error.params,
|
|
187
|
-
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// Add data if verbose enabled
|
|
205
|
+
if (ajvOptions.verbose) {
|
|
206
|
+
errorObj.data = error.data;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Use unshift instead of push so that the last error ends up at [0],
|
|
210
|
+
// preserving previous behavior where only the last error was stored.
|
|
211
|
+
array.unshift(errorObj);
|
|
188
212
|
|
|
189
213
|
++numErrors;
|
|
190
214
|
}
|
|
@@ -253,24 +277,35 @@ function objectHasDefaults(obj) {
|
|
|
253
277
|
|
|
254
278
|
function jsonSchemaWithoutRequired(jsonSchema) {
|
|
255
279
|
const subSchemaProps = ['anyOf', 'oneOf', 'allOf', 'not', 'then', 'else', 'properties'];
|
|
280
|
+
// `definitions` was renamed to `$defs` in draft 2019-09. Support both.
|
|
281
|
+
const definitionProps = ['definitions', '$defs'];
|
|
256
282
|
return Object.assign(
|
|
257
283
|
omit(jsonSchema, ['required', ...subSchemaProps]),
|
|
258
284
|
...subSchemaProps.map((prop) => subSchemaWithoutRequired(jsonSchema, prop)),
|
|
259
|
-
|
|
260
|
-
? {
|
|
261
|
-
definitions: Object.assign(
|
|
262
|
-
...Object.keys(jsonSchema.definitions).map((prop) => ({
|
|
263
|
-
[prop]: jsonSchemaWithoutRequired(jsonSchema.definitions[prop]),
|
|
264
|
-
})),
|
|
265
|
-
),
|
|
266
|
-
}
|
|
267
|
-
: {},
|
|
285
|
+
...definitionProps.map((prop) => definitionsWithoutRequired(jsonSchema, prop)),
|
|
268
286
|
jsonSchema.discriminator && jsonSchema.discriminator.propertyName
|
|
269
287
|
? { required: [jsonSchema.discriminator.propertyName] }
|
|
270
288
|
: {},
|
|
271
289
|
);
|
|
272
290
|
}
|
|
273
291
|
|
|
292
|
+
function definitionsWithoutRequired(jsonSchema, prop) {
|
|
293
|
+
const definitions = jsonSchema && jsonSchema[prop];
|
|
294
|
+
|
|
295
|
+
if (definitions && Object.keys(definitions).length > 0) {
|
|
296
|
+
return {
|
|
297
|
+
[prop]: Object.fromEntries(
|
|
298
|
+
Object.entries(definitions).map(([key, schema]) => [
|
|
299
|
+
key,
|
|
300
|
+
jsonSchemaWithoutRequired(schema),
|
|
301
|
+
]),
|
|
302
|
+
),
|
|
303
|
+
};
|
|
304
|
+
} else {
|
|
305
|
+
return {};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
274
309
|
function subSchemaWithoutRequired(jsonSchema, prop) {
|
|
275
310
|
if (jsonSchema[prop]) {
|
|
276
311
|
if (Array.isArray(jsonSchema[prop])) {
|
package/lib/model/Model.js
CHANGED
|
@@ -120,7 +120,7 @@ class Model {
|
|
|
120
120
|
return json;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
$formatJson(json) {
|
|
123
|
+
$formatJson(json, options) {
|
|
124
124
|
return json;
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -317,9 +317,6 @@ class Model {
|
|
|
317
317
|
|
|
318
318
|
static createValidator() {
|
|
319
319
|
return new AjvValidator({
|
|
320
|
-
onCreateAjv: (ajv) => {
|
|
321
|
-
/* Do Nothing by default */
|
|
322
|
-
},
|
|
323
320
|
options: {
|
|
324
321
|
allErrors: true,
|
|
325
322
|
validateSchema: false,
|
|
@@ -724,6 +721,8 @@ Model.uidProp = '#id';
|
|
|
724
721
|
Model.uidRefProp = '#ref';
|
|
725
722
|
Model.dbRefProp = '#dbRef';
|
|
726
723
|
Model.propRefRegex = /#ref{([^\.]+)\.([^}]+)}/g;
|
|
724
|
+
Model.graphUnrelateProp = '#unrelate';
|
|
725
|
+
Model.graphDeleteProp = '#delete';
|
|
727
726
|
Model.jsonAttributes = null;
|
|
728
727
|
Model.cloneObjectAttributes = true;
|
|
729
728
|
Model.virtualAttributes = null;
|
|
@@ -46,6 +46,18 @@ class ModelGraphNode {
|
|
|
46
46
|
return this.obj[this.modelClass.dbRefProp];
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
get toBeUnrelated() {
|
|
50
|
+
return this.obj[this.modelClass.graphUnrelateProp] === true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get toBeDeleted() {
|
|
54
|
+
return this.obj[this.modelClass.graphDeleteProp] === true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get toBeUnrelatedOrDeleted() {
|
|
58
|
+
return this.toBeUnrelated || this.toBeDeleted;
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
get parentNode() {
|
|
50
62
|
if (this.parentEdge) {
|
|
51
63
|
return this.parentEdge.ownerNode;
|
package/lib/model/modelToJson.js
CHANGED
|
@@ -15,10 +15,11 @@ function toJson(model, optIn) {
|
|
|
15
15
|
pick: null,
|
|
16
16
|
omitFromJson: model.$omitFromJson() || null,
|
|
17
17
|
cloneObjects: modelClass.cloneObjectAttributes,
|
|
18
|
+
format: optIn && optIn.format,
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
let json = toExternalJsonImpl(model, opt);
|
|
21
|
-
json = model.$formatJson(json);
|
|
22
|
+
json = model.$formatJson(json, opt.format);
|
|
22
23
|
|
|
23
24
|
return json;
|
|
24
25
|
}
|
|
@@ -43,6 +43,7 @@ const { SelectOperation } = require('./operations/select/SelectOperation');
|
|
|
43
43
|
const { EagerOperation } = require('./operations/eager/EagerOperation');
|
|
44
44
|
const { RangeOperation } = require('./operations/RangeOperation');
|
|
45
45
|
const { FirstOperation } = require('./operations/FirstOperation');
|
|
46
|
+
const { NoneOperation } = require('./operations/NoneOperation');
|
|
46
47
|
const { FromOperation } = require('./operations/FromOperation');
|
|
47
48
|
const { KnexOperation } = require('./operations/KnexOperation');
|
|
48
49
|
|
|
@@ -56,6 +57,7 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
56
57
|
self._modifiers = {};
|
|
57
58
|
|
|
58
59
|
self._allowedGraphExpression = null;
|
|
60
|
+
self._graphModifiersAtPath = [];
|
|
59
61
|
self._findOperationOptions = modelClass.defaultFindOptions;
|
|
60
62
|
self._relatedQueryFor = null;
|
|
61
63
|
|
|
@@ -203,6 +205,7 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
203
205
|
eagerOp.expression = eagerOp.expression.merge(parsedExp);
|
|
204
206
|
eagerOp.graphOptions = { ...eagerOp.graphOptions, ...options };
|
|
205
207
|
|
|
208
|
+
checkMixedEager(this);
|
|
206
209
|
checkEager(this);
|
|
207
210
|
return this;
|
|
208
211
|
}
|
|
@@ -223,28 +226,30 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
223
226
|
}
|
|
224
227
|
|
|
225
228
|
graphExpressionObject() {
|
|
226
|
-
const
|
|
229
|
+
const expression = getGraphExpression(this);
|
|
227
230
|
|
|
228
|
-
if (
|
|
229
|
-
return
|
|
231
|
+
if (expression && !expression.isEmpty) {
|
|
232
|
+
return expression.toPojo();
|
|
230
233
|
} else {
|
|
231
234
|
return null;
|
|
232
235
|
}
|
|
233
236
|
}
|
|
234
237
|
|
|
235
238
|
graphModifiersAtPath() {
|
|
236
|
-
const
|
|
239
|
+
const expression = getGraphExpression(this);
|
|
237
240
|
|
|
238
|
-
if (
|
|
239
|
-
return
|
|
241
|
+
if (expression && !expression.isEmpty) {
|
|
242
|
+
return this._graphModifiersAtPath.map((it) => Object.assign({}, it));
|
|
240
243
|
} else {
|
|
241
244
|
return [];
|
|
242
245
|
}
|
|
243
246
|
}
|
|
244
247
|
|
|
245
248
|
modifyGraph(path, modifier) {
|
|
246
|
-
|
|
247
|
-
|
|
249
|
+
// The modifiers are shared by all eager operations. They only take effect
|
|
250
|
+
// for the paths that exist in each operation's expression.
|
|
251
|
+
ensureEagerOperation(this);
|
|
252
|
+
this._graphModifiersAtPath.push({ path, modifier });
|
|
248
253
|
return this;
|
|
249
254
|
}
|
|
250
255
|
|
|
@@ -301,8 +306,7 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
301
306
|
}
|
|
302
307
|
|
|
303
308
|
hasWithGraph() {
|
|
304
|
-
|
|
305
|
-
return !!eagerOp && !eagerOp.expression.isEmpty;
|
|
309
|
+
return findEagerOperations(this).some((eagerOp) => !eagerOp.expression.isEmpty);
|
|
306
310
|
}
|
|
307
311
|
|
|
308
312
|
isSelectAll() {
|
|
@@ -350,6 +354,7 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
350
354
|
builder._modifiers = { ...this._modifiers };
|
|
351
355
|
|
|
352
356
|
builder._allowedGraphExpression = this._allowedGraphExpression;
|
|
357
|
+
builder._graphModifiersAtPath = this._graphModifiersAtPath.slice();
|
|
353
358
|
builder._findOperationOptions = this._findOperationOptions;
|
|
354
359
|
builder._relatedQueryFor = this._relatedQueryFor;
|
|
355
360
|
|
|
@@ -421,7 +426,9 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
421
426
|
const builder = this.clone().clear(/orderBy|offset|limit/);
|
|
422
427
|
|
|
423
428
|
const countQuery = knex.count('* as count').from((knexBuilder) => {
|
|
424
|
-
|
|
429
|
+
// Also clear the order on the built knex query, since `orderBy` calls
|
|
430
|
+
// can be added at build time, e.g. by a relation's `modify`.
|
|
431
|
+
builder.toKnexQuery(knexBuilder).clearOrder().as('temp');
|
|
425
432
|
});
|
|
426
433
|
|
|
427
434
|
if (this.internalOptions().debug) {
|
|
@@ -808,6 +815,10 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
808
815
|
return this.addOperation(new FirstOperation('first'), args);
|
|
809
816
|
}
|
|
810
817
|
|
|
818
|
+
none() {
|
|
819
|
+
return this.addOperation(new NoneOperation('none'), []);
|
|
820
|
+
}
|
|
821
|
+
|
|
811
822
|
joinRelated(expression, options) {
|
|
812
823
|
ensureJoinRelatedOperation(this, 'innerJoin').addCall({
|
|
813
824
|
expression,
|
|
@@ -922,6 +933,10 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
922
933
|
return this.addOperation(new FromOperation('from'), args);
|
|
923
934
|
}
|
|
924
935
|
|
|
936
|
+
updateFrom(...args) {
|
|
937
|
+
return this.addOperation(new KnexOperation('updateFrom'), args);
|
|
938
|
+
}
|
|
939
|
+
|
|
925
940
|
table(...args) {
|
|
926
941
|
return this.addOperation(new FromOperation('table'), args);
|
|
927
942
|
}
|
|
@@ -930,6 +945,15 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
930
945
|
if (arguments.length === 0) {
|
|
931
946
|
return this._relatedQueryFor;
|
|
932
947
|
} else {
|
|
948
|
+
// The write operation factories installed by the static `relatedQuery()`
|
|
949
|
+
// read `for()` when the write method is called. Setting it afterwards
|
|
950
|
+
// would make the query silently target the wrong rows.
|
|
951
|
+
if (this._findOperationFactory !== findOperationFactory && !this.isFind()) {
|
|
952
|
+
throw new Error(
|
|
953
|
+
'for() must be called before insert, update, patch, delete, relate or unrelate on queries created using the static relatedQuery method',
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
|
|
933
957
|
this._relatedQueryFor = relatedQueryFor;
|
|
934
958
|
return this;
|
|
935
959
|
}
|
|
@@ -952,48 +976,83 @@ function getTableName(modelClassOrTableName) {
|
|
|
952
976
|
}
|
|
953
977
|
}
|
|
954
978
|
|
|
979
|
+
// A query builder can hold at most one eager operation of each kind: one
|
|
980
|
+
// `JoinEagerOperation` for `withGraphJoined` and one `WhereInEagerOperation`
|
|
981
|
+
// (or its subclass `NaiveEagerOperation`) for `withGraphFetched`.
|
|
955
982
|
function ensureEagerOperation(builder, algorithm = null) {
|
|
956
983
|
const modelClass = builder.modelClass();
|
|
957
984
|
const defaultGraphOptions = modelClass.getDefaultGraphOptions();
|
|
958
|
-
const eagerOp = builder.findOperation(EagerOperation);
|
|
959
985
|
|
|
960
|
-
if (algorithm) {
|
|
961
|
-
const
|
|
986
|
+
if (!algorithm) {
|
|
987
|
+
const eagerOp = builder.findOperation(EagerOperation);
|
|
962
988
|
|
|
963
|
-
if (eagerOp
|
|
989
|
+
if (eagerOp) {
|
|
964
990
|
return eagerOp;
|
|
965
|
-
}
|
|
966
|
-
const newEagerOp = new EagerOperationClass('eager', {
|
|
967
|
-
defaultGraphOptions,
|
|
968
|
-
});
|
|
991
|
+
}
|
|
969
992
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
}
|
|
993
|
+
algorithm = getWhereInEagerAlgorithm(builder);
|
|
994
|
+
}
|
|
973
995
|
|
|
974
|
-
|
|
975
|
-
|
|
996
|
+
const EagerOperationClass = getOperationClassForEagerAlgorithm(builder, algorithm);
|
|
997
|
+
const EagerOperationKind = getEagerOperationKind(EagerOperationClass);
|
|
998
|
+
const eagerOp = builder.findOperation(EagerOperationKind);
|
|
976
999
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
}
|
|
980
|
-
if (eagerOp) {
|
|
981
|
-
return eagerOp;
|
|
982
|
-
} else {
|
|
983
|
-
const EagerOperationClass = getOperationClassForEagerAlgorithm(
|
|
984
|
-
builder,
|
|
985
|
-
getWhereInEagerAlgorithm(builder),
|
|
986
|
-
);
|
|
1000
|
+
if (eagerOp && eagerOp.constructor === EagerOperationClass) {
|
|
1001
|
+
return eagerOp;
|
|
1002
|
+
}
|
|
987
1003
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1004
|
+
const newEagerOp = new EagerOperationClass('eager', {
|
|
1005
|
+
defaultGraphOptions,
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
if (eagerOp) {
|
|
1009
|
+
// Switching between algorithms of the same kind, e.g. from
|
|
1010
|
+
// `WhereInEagerOperation` to `NaiveEagerOperation`.
|
|
1011
|
+
newEagerOp.cloneFrom(eagerOp);
|
|
1012
|
+
builder.clear(EagerOperationKind);
|
|
1013
|
+
} else if (!builder.has(EagerOperation)) {
|
|
1014
|
+
// The `modifyGraph` modifiers only live as long as an eager operation.
|
|
1015
|
+
builder._graphModifiersAtPath = [];
|
|
1016
|
+
}
|
|
991
1017
|
|
|
992
|
-
|
|
1018
|
+
builder.addOperation(newEagerOp);
|
|
993
1019
|
|
|
994
|
-
|
|
995
|
-
|
|
1020
|
+
// Keep the joined operation before the fetched one (see `findEagerOperations`),
|
|
1021
|
+
// even if it is added after `addImplicitOperations`, e.g. in `runBefore`.
|
|
1022
|
+
const fetchOp =
|
|
1023
|
+
EagerOperationKind === JoinEagerOperation && builder.findOperation(WhereInEagerOperation);
|
|
1024
|
+
|
|
1025
|
+
if (fetchOp) {
|
|
1026
|
+
builder.removeOperation(fetchOp).addOperation(fetchOp);
|
|
996
1027
|
}
|
|
1028
|
+
|
|
1029
|
+
return newEagerOp;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function getEagerOperationKind(EagerOperationClass) {
|
|
1033
|
+
return EagerOperationClass === JoinEagerOperation ? JoinEagerOperation : WhereInEagerOperation;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function findEagerOperations(builder) {
|
|
1037
|
+
// Joined operations first, so that their `onBuild` runs before the fetched
|
|
1038
|
+
// one's. See `WhereInEagerOperation.onBuild`.
|
|
1039
|
+
return [
|
|
1040
|
+
builder.findOperation(JoinEagerOperation),
|
|
1041
|
+
builder.findOperation(WhereInEagerOperation),
|
|
1042
|
+
].filter(Boolean);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function getGraphExpression(builder) {
|
|
1046
|
+
const eagerOps = findEagerOperations(builder);
|
|
1047
|
+
|
|
1048
|
+
if (eagerOps.length === 0) {
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
return eagerOps.reduce(
|
|
1053
|
+
(expression, eagerOp) => expression.merge(eagerOp.expression),
|
|
1054
|
+
RelationExpression.create(),
|
|
1055
|
+
);
|
|
997
1056
|
}
|
|
998
1057
|
|
|
999
1058
|
function getWhereInEagerAlgorithm(builder) {
|
|
@@ -1036,14 +1095,44 @@ function parseRelationExpression(modelClass, exp) {
|
|
|
1036
1095
|
}
|
|
1037
1096
|
}
|
|
1038
1097
|
|
|
1098
|
+
function checkMixedEager(builder) {
|
|
1099
|
+
const joinOp = builder.findOperation(JoinEagerOperation);
|
|
1100
|
+
const fetchOp = builder.findOperation(WhereInEagerOperation);
|
|
1101
|
+
|
|
1102
|
+
if (!joinOp || !fetchOp) {
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
const joinNode = joinOp.expression.node;
|
|
1107
|
+
const fetchNode = fetchOp.expression.node;
|
|
1108
|
+
const isUsed = (node) => node.$allRecursive || node.$childNames.length > 0;
|
|
1109
|
+
|
|
1110
|
+
if (!isUsed(joinNode) || !isUsed(fetchNode)) {
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (joinNode.$allRecursive || fetchNode.$allRecursive) {
|
|
1115
|
+
throw new Error(
|
|
1116
|
+
'relation expression `*` cannot be used when mixing withGraphJoined and withGraphFetched',
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
for (const childName of joinNode.$childNames) {
|
|
1121
|
+
if (fetchNode.$childNames.includes(childName)) {
|
|
1122
|
+
throw new Error(
|
|
1123
|
+
`relation \`${childName}\` cannot be loaded with both withGraphJoined and withGraphFetched`,
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1039
1129
|
function checkEager(builder) {
|
|
1040
|
-
const
|
|
1130
|
+
const expr = getGraphExpression(builder);
|
|
1041
1131
|
|
|
1042
|
-
if (!
|
|
1132
|
+
if (!expr) {
|
|
1043
1133
|
return;
|
|
1044
1134
|
}
|
|
1045
1135
|
|
|
1046
|
-
const expr = eagerOp.expression;
|
|
1047
1136
|
const allowedExpr = builder.allowedGraphExpression();
|
|
1048
1137
|
|
|
1049
1138
|
if (expr.numChildren > 0 && allowedExpr && !allowedExpr.isSubExpression(expr)) {
|
|
@@ -1065,6 +1154,7 @@ function findQueryExecutorOperation(builder) {
|
|
|
1065
1154
|
function beforeExecute(builder) {
|
|
1066
1155
|
let promise = Promise.resolve();
|
|
1067
1156
|
|
|
1157
|
+
checkRelatedQueryFor(builder);
|
|
1068
1158
|
builder = addImplicitOperations(builder);
|
|
1069
1159
|
|
|
1070
1160
|
// Resolve all before hooks before building and executing the query
|
|
@@ -1181,6 +1271,7 @@ function ensureJoinRelatedOperation(builder, joinOperation) {
|
|
|
1181
1271
|
}
|
|
1182
1272
|
|
|
1183
1273
|
function prebuildQuery(builder) {
|
|
1274
|
+
checkRelatedQueryFor(builder);
|
|
1184
1275
|
builder = addImplicitOperations(builder);
|
|
1185
1276
|
builder = callOnBuildHooks(builder);
|
|
1186
1277
|
|
|
@@ -1193,6 +1284,17 @@ function prebuildQuery(builder) {
|
|
|
1193
1284
|
}
|
|
1194
1285
|
}
|
|
1195
1286
|
|
|
1287
|
+
function checkRelatedQueryFor(builder) {
|
|
1288
|
+
// `for()` is only consumed by the operation factories installed by the static
|
|
1289
|
+
// `relatedQuery()`. The default ones ignore it, which would silently make the
|
|
1290
|
+
// query target all rows of the table.
|
|
1291
|
+
if (builder.for() !== null && builder._findOperationFactory === findOperationFactory) {
|
|
1292
|
+
throw new Error(
|
|
1293
|
+
'for() can only be used with queries created using the static relatedQuery method',
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1196
1298
|
function addImplicitOperations(builder) {
|
|
1197
1299
|
if (builder.isFind()) {
|
|
1198
1300
|
// If no write operations have been called at this point this query is a
|
|
@@ -1215,10 +1317,13 @@ function addFindOperation(builder) {
|
|
|
1215
1317
|
}
|
|
1216
1318
|
|
|
1217
1319
|
function moveEagerOperationToEnd(builder) {
|
|
1218
|
-
const
|
|
1320
|
+
const eagerOps = findEagerOperations(builder);
|
|
1219
1321
|
|
|
1220
1322
|
builder.clear(EagerOperation);
|
|
1221
|
-
|
|
1323
|
+
|
|
1324
|
+
for (const eagerOp of eagerOps) {
|
|
1325
|
+
builder.addOperation(eagerOp);
|
|
1326
|
+
}
|
|
1222
1327
|
}
|
|
1223
1328
|
|
|
1224
1329
|
function callOnBuildHooks(builder) {
|
|
@@ -1240,6 +1345,12 @@ function callOnBuildFuncs(builder, func) {
|
|
|
1240
1345
|
function buildKnexQuery(builder, knexBuilder = builder.knex().queryBuilder()) {
|
|
1241
1346
|
knexBuilder = builder.executeOnBuildKnex(knexBuilder);
|
|
1242
1347
|
|
|
1348
|
+
const noneOperation = builder.findOperation(NoneOperation);
|
|
1349
|
+
|
|
1350
|
+
if (noneOperation) {
|
|
1351
|
+
knexBuilder = noneOperation.onAfterBuildKnex(knexBuilder);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1243
1354
|
const fromOperation = builder.findLastOperation(QueryBuilderBase.FromSelector);
|
|
1244
1355
|
|
|
1245
1356
|
if (!builder.isPartial()) {
|
|
@@ -12,7 +12,7 @@ const WhereSelector = /^(where|orWhere|andWhere|find\w+)/;
|
|
|
12
12
|
const OnSelector = /^(on|orOn|andOn)/;
|
|
13
13
|
const OrderBySelector = /orderBy/;
|
|
14
14
|
const JoinSelector = /(join|joinRaw|joinRelated)$/i;
|
|
15
|
-
const FromSelector = /^(from|into|table)$/;
|
|
15
|
+
const FromSelector = /^(from|fromRaw|into|table)$/;
|
|
16
16
|
|
|
17
17
|
class QueryBuilderOperationSupport {
|
|
18
18
|
constructor(...args) {
|
|
@@ -229,6 +229,14 @@ function toString(node) {
|
|
|
229
229
|
let childExpr = childNames.map((childName) => node[childName]).map(toString);
|
|
230
230
|
let str = node.$relation;
|
|
231
231
|
|
|
232
|
+
if (node.$modify.length) {
|
|
233
|
+
str += `(${node.$modify.join(', ')})`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (node.$name !== node.$relation) {
|
|
237
|
+
str += ` as ${node.$name}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
232
240
|
if (node.$recursive) {
|
|
233
241
|
if (isNumber(node.$recursive)) {
|
|
234
242
|
str += '.^' + node.$recursive;
|
|
@@ -245,14 +253,6 @@ function toString(node) {
|
|
|
245
253
|
childExpr = childExpr[0];
|
|
246
254
|
}
|
|
247
255
|
|
|
248
|
-
if (node.$modify.length) {
|
|
249
|
-
str += `(${node.$modify.join(', ')})`;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (node.$name !== node.$relation) {
|
|
253
|
-
str += ` as ${node.$name}`;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
256
|
if (childExpr) {
|
|
257
257
|
if (str) {
|
|
258
258
|
return `${str}.${childExpr}`;
|
|
@@ -141,7 +141,9 @@ function shouldSelectColumn(column, selects, node) {
|
|
|
141
141
|
!selects.has(column) &&
|
|
142
142
|
column !== modelClass.propertyNameToColumnName(modelClass.dbRefProp) &&
|
|
143
143
|
column !== modelClass.propertyNameToColumnName(modelClass.uidRefProp) &&
|
|
144
|
-
column !== modelClass.propertyNameToColumnName(modelClass.uidProp)
|
|
144
|
+
column !== modelClass.propertyNameToColumnName(modelClass.uidProp) &&
|
|
145
|
+
column !== modelClass.propertyNameToColumnName(modelClass.graphUnrelateProp) &&
|
|
146
|
+
column !== modelClass.propertyNameToColumnName(modelClass.graphDeleteProp)
|
|
145
147
|
);
|
|
146
148
|
}
|
|
147
149
|
|