@abedmurrar/objection 0.0.0-stage → 4.0.0-alpha.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/LICENSE +23 -0
- package/README.md +69 -2
- package/lib/.eslintrc.json +8 -0
- package/lib/initialize.js +14 -0
- package/lib/model/AjvValidator.js +325 -0
- package/lib/model/Model.js +942 -0
- package/lib/model/ModifierNotFoundError.js +12 -0
- package/lib/model/NotFoundError.js +23 -0
- package/lib/model/RelationDoesNotExistError.js +14 -0
- package/lib/model/ValidationError.js +54 -0
- package/lib/model/Validator.js +26 -0
- package/lib/model/getModel.js +9 -0
- package/lib/model/graph/ModelGraph.js +80 -0
- package/lib/model/graph/ModelGraphBuilder.js +241 -0
- package/lib/model/graph/ModelGraphEdge.js +52 -0
- package/lib/model/graph/ModelGraphNode.js +240 -0
- package/lib/model/inheritModel.js +28 -0
- package/lib/model/modelBindKnex.js +83 -0
- package/lib/model/modelClone.js +121 -0
- package/lib/model/modelColPropMap.js +34 -0
- package/lib/model/modelId.js +62 -0
- package/lib/model/modelJsonAttributes.js +92 -0
- package/lib/model/modelParseRelations.js +82 -0
- package/lib/model/modelQueryProps.js +147 -0
- package/lib/model/modelSet.js +136 -0
- package/lib/model/modelTableMetadata.js +83 -0
- package/lib/model/modelToJson.js +187 -0
- package/lib/model/modelUtils.js +47 -0
- package/lib/model/modelValidate.js +48 -0
- package/lib/model/modelValues.js +126 -0
- package/lib/model/modelVisitor.js +41 -0
- package/lib/objection.js +114 -0
- package/lib/queryBuilder/FunctionBuilder.js +339 -0
- package/lib/queryBuilder/InternalOptions.js +29 -0
- package/lib/queryBuilder/JoinBuilder.js +157 -0
- package/lib/queryBuilder/QueryBuilder.js +1398 -0
- package/lib/queryBuilder/QueryBuilderBase.js +884 -0
- package/lib/queryBuilder/QueryBuilderContext.js +27 -0
- package/lib/queryBuilder/QueryBuilderContextBase.js +35 -0
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +558 -0
- package/lib/queryBuilder/QueryBuilderUserContext.js +31 -0
- package/lib/queryBuilder/RawBuilder.js +93 -0
- package/lib/queryBuilder/ReferenceBuilder.js +284 -0
- package/lib/queryBuilder/RelationExpression.js +431 -0
- package/lib/queryBuilder/StaticHookArguments.js +134 -0
- package/lib/queryBuilder/ValueBuilder.js +127 -0
- package/lib/queryBuilder/expressions.js +151 -0
- package/lib/queryBuilder/functions/mysql.js +817 -0
- package/lib/queryBuilder/graph/GraphAction.js +69 -0
- package/lib/queryBuilder/graph/GraphData.js +14 -0
- package/lib/queryBuilder/graph/GraphFetcher.js +191 -0
- package/lib/queryBuilder/graph/GraphNodeDbExistence.js +184 -0
- package/lib/queryBuilder/graph/GraphOperation.js +27 -0
- package/lib/queryBuilder/graph/GraphOptions.js +207 -0
- package/lib/queryBuilder/graph/GraphUpsert.js +305 -0
- package/lib/queryBuilder/graph/delete/GraphDelete.js +20 -0
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +74 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +145 -0
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +225 -0
- package/lib/queryBuilder/graph/insert/JoinRowGraphInsertAction.js +92 -0
- package/lib/queryBuilder/graph/patch/GraphPatch.js +20 -0
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +258 -0
- package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsert.js +31 -0
- package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsertAction.js +126 -0
- package/lib/queryBuilder/join/JoinResultColumn.js +23 -0
- package/lib/queryBuilder/join/JoinResultParser.js +119 -0
- package/lib/queryBuilder/join/RelationJoiner.js +341 -0
- package/lib/queryBuilder/join/TableNode.js +163 -0
- package/lib/queryBuilder/join/TableTree.js +85 -0
- package/lib/queryBuilder/join/utils.js +25 -0
- package/lib/queryBuilder/operations/DelegateOperation.js +141 -0
- package/lib/queryBuilder/operations/DeleteOperation.js +51 -0
- package/lib/queryBuilder/operations/FindByIdOperation.js +37 -0
- package/lib/queryBuilder/operations/FindByIdsOperation.js +29 -0
- package/lib/queryBuilder/operations/FindOperation.js +164 -0
- package/lib/queryBuilder/operations/FirstOperation.js +27 -0
- package/lib/queryBuilder/operations/FromOperation.js +110 -0
- package/lib/queryBuilder/operations/InsertAndFetchOperation.js +49 -0
- package/lib/queryBuilder/operations/InsertGraphAndFetchOperation.js +45 -0
- package/lib/queryBuilder/operations/InsertGraphOperation.js +108 -0
- package/lib/queryBuilder/operations/InsertOperation.js +206 -0
- package/lib/queryBuilder/operations/InstanceDeleteOperation.js +46 -0
- package/lib/queryBuilder/operations/InstanceFindOperation.js +21 -0
- package/lib/queryBuilder/operations/InstanceInsertOperation.js +24 -0
- package/lib/queryBuilder/operations/InstanceUpdateOperation.js +59 -0
- package/lib/queryBuilder/operations/JoinRelatedOperation.js +103 -0
- package/lib/queryBuilder/operations/KnexOperation.js +14 -0
- package/lib/queryBuilder/operations/MergeOperation.js +56 -0
- package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +192 -0
- package/lib/queryBuilder/operations/OnBuildKnexOperation.js +29 -0
- package/lib/queryBuilder/operations/OnBuildOperation.js +29 -0
- package/lib/queryBuilder/operations/OnErrorOperation.js +29 -0
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +297 -0
- package/lib/queryBuilder/operations/RangeOperation.js +47 -0
- package/lib/queryBuilder/operations/RelateOperation.js +27 -0
- package/lib/queryBuilder/operations/ReturningOperation.js +30 -0
- package/lib/queryBuilder/operations/RunAfterOperation.js +29 -0
- package/lib/queryBuilder/operations/RunBeforeOperation.js +29 -0
- package/lib/queryBuilder/operations/UnrelateOperation.js +23 -0
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +80 -0
- package/lib/queryBuilder/operations/UpdateOperation.js +155 -0
- package/lib/queryBuilder/operations/UpsertGraphAndFetchOperation.js +45 -0
- package/lib/queryBuilder/operations/UpsertGraphOperation.js +66 -0
- package/lib/queryBuilder/operations/WhereCompositeOperation.js +57 -0
- package/lib/queryBuilder/operations/WhereInOperation.js +48 -0
- package/lib/queryBuilder/operations/eager/EagerOperation.js +61 -0
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +51 -0
- package/lib/queryBuilder/operations/eager/NaiveEagerOperation.js +13 -0
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +285 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonHasOperation.js +36 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectOperation.js +53 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonOperation.js +31 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonOverlapsOperation.js +31 -0
- package/lib/queryBuilder/operations/jsonApi/mysqlJsonApi.js +167 -0
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +164 -0
- package/lib/queryBuilder/operations/select/SelectOperation.js +66 -0
- package/lib/queryBuilder/operations/select/Selection.js +123 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +117 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +100 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +104 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +886 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +97 -0
- package/lib/queryBuilder/parsers/relationExpressionParser.js +1042 -0
- package/lib/queryBuilder/parsers/relationExpressionParser.pegjs +141 -0
- package/lib/queryBuilder/transformations/CompositeQueryTransformation.js +22 -0
- package/lib/queryBuilder/transformations/QueryTransformation.js +11 -0
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +71 -0
- package/lib/queryBuilder/transformations/index.js +15 -0
- package/lib/queryBuilder/whereScopes.js +227 -0
- package/lib/relations/Relation.js +401 -0
- package/lib/relations/RelationDeleteOperation.js +29 -0
- package/lib/relations/RelationFindOperation.js +149 -0
- package/lib/relations/RelationInsertOperation.js +33 -0
- package/lib/relations/RelationOwner.js +236 -0
- package/lib/relations/RelationProperty.js +247 -0
- package/lib/relations/RelationUpdateOperation.js +30 -0
- package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +23 -0
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +44 -0
- package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +50 -0
- package/lib/relations/belongsToOne/BelongsToOneRelation.js +53 -0
- package/lib/relations/belongsToOne/BelongsToOneUnrelateOperation.js +61 -0
- package/lib/relations/hasMany/HasManyInsertOperation.js +61 -0
- package/lib/relations/hasMany/HasManyRelateOperation.js +64 -0
- package/lib/relations/hasMany/HasManyRelation.js +49 -0
- package/lib/relations/hasMany/HasManyUnrelateOperation.js +33 -0
- package/lib/relations/hasOne/HasOneRelation.js +13 -0
- package/lib/relations/hasOneThrough/HasOneThroughRelation.js +13 -0
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +105 -0
- package/lib/relations/manyToMany/ManyToManyRelation.js +507 -0
- package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +36 -0
- package/lib/relations/manyToMany/delete/ManyToManyDeleteOperation.js +10 -0
- package/lib/relations/manyToMany/delete/ManyToManyDeleteOperationBase.js +26 -0
- package/lib/relations/manyToMany/delete/ManyToManyDeleteSqliteOperation.js +12 -0
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +105 -0
- package/lib/relations/manyToMany/insert/ManyToManyInsertOperation.js +52 -0
- package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +81 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +14 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +34 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +16 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperation.js +10 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +82 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateSqliteOperation.js +12 -0
- package/lib/transaction.js +96 -0
- package/lib/utils/assert.js +25 -0
- package/lib/utils/buildUtils.js +21 -0
- package/lib/utils/classUtils.js +13 -0
- package/lib/utils/clone.js +2329 -0
- package/lib/utils/createModifier.js +42 -0
- package/lib/utils/deprecate.js +15 -0
- package/lib/utils/identifierMapping.js +227 -0
- package/lib/utils/internalPropUtils.js +11 -0
- package/lib/utils/knexUtils.js +76 -0
- package/lib/utils/mixin.js +25 -0
- package/lib/utils/mysqlJsonPath.js +55 -0
- package/lib/utils/normalizeIds.js +144 -0
- package/lib/utils/objectUtils.js +416 -0
- package/lib/utils/parseFieldExpression.js +51 -0
- package/lib/utils/postgresJsonPath.js +110 -0
- package/lib/utils/promiseUtils/after.js +17 -0
- package/lib/utils/promiseUtils/afterReturn.js +17 -0
- package/lib/utils/promiseUtils/index.js +17 -0
- package/lib/utils/promiseUtils/isPromise.js +11 -0
- package/lib/utils/promiseUtils/map.js +110 -0
- package/lib/utils/promiseUtils/mapAfterAllReturn.js +29 -0
- package/lib/utils/promiseUtils/try.js +22 -0
- package/lib/utils/resolveModel.js +107 -0
- package/lib/utils/tmpColumnUtils.js +16 -0
- package/package.json +86 -4
- package/typings/objection/index.d.ts +4104 -0
- package/typings/objection/relation-expression.d.ts +237 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { GraphAction } = require('../GraphAction');
|
|
4
|
+
const { groupBy, get, set } = require('../../../utils/objectUtils');
|
|
5
|
+
const { forEachPropertyReference } = require('../../../model/graph/ModelGraphBuilder');
|
|
6
|
+
const promiseUtils = require('../../../utils/promiseUtils');
|
|
7
|
+
|
|
8
|
+
class GraphRecursiveUpsertAction extends GraphAction {
|
|
9
|
+
constructor(graphData, { nodes }) {
|
|
10
|
+
super(graphData);
|
|
11
|
+
// Nodes to upsert.
|
|
12
|
+
this.nodes = nodes;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
run(builder) {
|
|
16
|
+
const builders = this._createUpsertBuilders(builder, this.nodes);
|
|
17
|
+
|
|
18
|
+
return promiseUtils.map(builders, (builder) => builder.execute(), {
|
|
19
|
+
concurrency: this._getConcurrency(builder, this.nodes),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_createUpsertBuilders(parentBuilder, nodesToUpsert) {
|
|
24
|
+
const nodesByRelation = groupBy(nodesToUpsert, getRelation);
|
|
25
|
+
const builders = [];
|
|
26
|
+
|
|
27
|
+
nodesByRelation.forEach((nodes) => {
|
|
28
|
+
const nodesByParent = groupBy(nodes, getParent);
|
|
29
|
+
|
|
30
|
+
nodesByParent.forEach((nodes) => {
|
|
31
|
+
for (const node of nodes) {
|
|
32
|
+
this._resolveReferences(node);
|
|
33
|
+
node.userData.upserted = true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
builders.push(
|
|
37
|
+
nodes[0].modelClass
|
|
38
|
+
.query()
|
|
39
|
+
.childQueryOf(parentBuilder)
|
|
40
|
+
.copyFrom(parentBuilder, GraphAction.ReturningAllSelector)
|
|
41
|
+
.upsertGraph(
|
|
42
|
+
nodes.map((node) => node.obj),
|
|
43
|
+
this.graphOptions.rebasedOptions(nodes[0]),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return builders;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The nodes inside the subgraph we are about to recursively upsert may
|
|
54
|
+
* have references outside that graph that won't be available during the
|
|
55
|
+
* recursive upsertGraph call. This method resolves the references.
|
|
56
|
+
*
|
|
57
|
+
* TODO: This doesn't work if a recursively upserted node refers to
|
|
58
|
+
* a node inside another recursively upsertable graph.
|
|
59
|
+
*/
|
|
60
|
+
_resolveReferences(node) {
|
|
61
|
+
node.obj.$traverse((obj) => this._resolveReferencesForObject(obj));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_resolveReferencesForObject(obj) {
|
|
65
|
+
this._resolveObjectReference(obj);
|
|
66
|
+
this._resolvePropertyReferences(obj);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_resolveObjectReference(obj) {
|
|
70
|
+
const modelClass = obj.constructor;
|
|
71
|
+
const ref = obj[modelClass.uidRefProp];
|
|
72
|
+
|
|
73
|
+
if (!ref) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const referencedNode = this.graph.nodes.find((it) => it.uid === ref);
|
|
78
|
+
|
|
79
|
+
if (!referencedNode) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const relationNames = referencedNode.modelClass.getRelationNames();
|
|
84
|
+
|
|
85
|
+
for (const prop of Object.keys(referencedNode.obj)) {
|
|
86
|
+
if (relationNames.includes(prop)) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
obj[prop] = referencedNode.obj[prop];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
delete obj[modelClass.uidRefProp];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_resolvePropertyReferences(obj) {
|
|
97
|
+
forEachPropertyReference(obj, ({ path, refMatch, ref, refPath }) => {
|
|
98
|
+
const referencedNode = this.graph.nodes.find((it) => it.uid === ref);
|
|
99
|
+
|
|
100
|
+
if (!referencedNode) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const referencedValue = get(referencedNode.obj, refPath);
|
|
105
|
+
const value = get(obj, path);
|
|
106
|
+
|
|
107
|
+
if (value === refMatch) {
|
|
108
|
+
set(obj, path, referencedValue);
|
|
109
|
+
} else {
|
|
110
|
+
set(obj, path, value.replace(refMatch, referencedValue));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getRelation(node) {
|
|
117
|
+
return node.parentEdge.relation;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getParent(node) {
|
|
121
|
+
return node.parentNode;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
GraphRecursiveUpsertAction,
|
|
126
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class JoinResultColumn {
|
|
4
|
+
constructor({ columnAlias, tableNode, name }) {
|
|
5
|
+
this.columnAlias = columnAlias;
|
|
6
|
+
this.tableNode = tableNode;
|
|
7
|
+
this.name = name;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static create({ tableTree, columnAlias }) {
|
|
11
|
+
const tableNode = tableTree.getNodeForColumnAlias(columnAlias);
|
|
12
|
+
|
|
13
|
+
return new JoinResultColumn({
|
|
14
|
+
columnAlias,
|
|
15
|
+
tableNode,
|
|
16
|
+
name: tableNode.getColumnForColumnAlias(columnAlias),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
JoinResultColumn,
|
|
23
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { JoinResultColumn } = require('./JoinResultColumn');
|
|
4
|
+
const { groupBy } = require('../../utils/objectUtils');
|
|
5
|
+
|
|
6
|
+
class JoinResultParser {
|
|
7
|
+
constructor({ tableTree, omitColumnAliases = [] }) {
|
|
8
|
+
this.tableTree = tableTree;
|
|
9
|
+
this.omitColumnAliases = new Set(omitColumnAliases);
|
|
10
|
+
|
|
11
|
+
this.columnsByTableNode = null;
|
|
12
|
+
this.parentMap = null;
|
|
13
|
+
this.rootModels = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static create(args) {
|
|
17
|
+
return new JoinResultParser(args);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
parse(flatRows) {
|
|
21
|
+
if (!Array.isArray(flatRows) || flatRows.length === 0) {
|
|
22
|
+
return flatRows;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.columnsByTableNode = this._createColumns(flatRows[0]);
|
|
26
|
+
this.parentMap = new Map();
|
|
27
|
+
this.rootModels = [];
|
|
28
|
+
|
|
29
|
+
for (const flatRow of flatRows) {
|
|
30
|
+
this._parseNode(this.tableTree.rootNode, flatRow);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return this.rootModels;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_parseNode(tableNode, flatRow, parentModel = null, parentKey = null) {
|
|
37
|
+
const id = tableNode.getIdFromFlatRow(flatRow);
|
|
38
|
+
|
|
39
|
+
if (id === null) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const key = getKey(parentKey, id, tableNode);
|
|
44
|
+
let model = this.parentMap.get(key);
|
|
45
|
+
|
|
46
|
+
if (!model) {
|
|
47
|
+
model = this._createModel(tableNode, flatRow);
|
|
48
|
+
|
|
49
|
+
this._addToParent(tableNode, model, parentModel);
|
|
50
|
+
this.parentMap.set(key, model);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const childNode of tableNode.childNodes) {
|
|
54
|
+
this._parseNode(childNode, flatRow, model, key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_createModel(tableNode, flatRow) {
|
|
59
|
+
const row = {};
|
|
60
|
+
const columns = this.columnsByTableNode.get(tableNode);
|
|
61
|
+
|
|
62
|
+
if (columns) {
|
|
63
|
+
for (const column of columns) {
|
|
64
|
+
if (!this.omitColumnAliases.has(column.columnAlias)) {
|
|
65
|
+
row[column.name] = flatRow[column.columnAlias];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const model = tableNode.modelClass.fromDatabaseJson(row);
|
|
71
|
+
|
|
72
|
+
for (const childNode of tableNode.childNodes) {
|
|
73
|
+
if (childNode.relation.isOneToOne()) {
|
|
74
|
+
model[childNode.relationProperty] = null;
|
|
75
|
+
} else {
|
|
76
|
+
model[childNode.relationProperty] = [];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return model;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_addToParent(tableNode, model, parentModel) {
|
|
84
|
+
if (tableNode.parentNode) {
|
|
85
|
+
if (tableNode.relation.isOneToOne()) {
|
|
86
|
+
parentModel[tableNode.relationProperty] = model;
|
|
87
|
+
} else {
|
|
88
|
+
parentModel[tableNode.relationProperty].push(model);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
// Root model. Add to root list.
|
|
92
|
+
this.rootModels.push(model);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_createColumns(row) {
|
|
97
|
+
const columns = Object.keys(row).map((columnAlias) =>
|
|
98
|
+
JoinResultColumn.create({ tableTree: this.tableTree, columnAlias }),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return groupBy(columns, getTableNode);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getTableNode(column) {
|
|
106
|
+
return column.tableNode;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getKey(parentKey, id, tableNode) {
|
|
110
|
+
if (parentKey !== null) {
|
|
111
|
+
return `${parentKey}/${tableNode.relationProperty}/${id}`;
|
|
112
|
+
} else {
|
|
113
|
+
return `/${id}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
JoinResultParser,
|
|
119
|
+
};
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { uniqBy } = require('../../utils/objectUtils');
|
|
4
|
+
const { Selection } = require('../operations/select/Selection');
|
|
5
|
+
const { createModifier } = require('../../utils/createModifier');
|
|
6
|
+
const { map: mapPromise } = require('../../utils/promiseUtils');
|
|
7
|
+
const { ValidationErrorType } = require('../../model/ValidationError');
|
|
8
|
+
|
|
9
|
+
const { TableTree } = require('./TableTree');
|
|
10
|
+
const { JoinResultParser } = require('./JoinResultParser');
|
|
11
|
+
const { ID_LENGTH_LIMIT } = require('./utils');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Given a relation expression, builds a query using joins to fetch it.
|
|
15
|
+
*/
|
|
16
|
+
class RelationJoiner {
|
|
17
|
+
constructor({ modelClass }) {
|
|
18
|
+
this.rootModelClass = modelClass;
|
|
19
|
+
|
|
20
|
+
// The relation expression to join.
|
|
21
|
+
this.expression = null;
|
|
22
|
+
|
|
23
|
+
// Explicit modifiers for the relation expression.
|
|
24
|
+
this.modifiers = null;
|
|
25
|
+
|
|
26
|
+
this.options = defaultOptions();
|
|
27
|
+
this.tableTree = null;
|
|
28
|
+
this.internalSelections = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setExpression(expression) {
|
|
32
|
+
if (!this.expression) {
|
|
33
|
+
this.expression = expression;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setModifiers(modifiers) {
|
|
40
|
+
if (!this.modifiers) {
|
|
41
|
+
this.modifiers = modifiers;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setOptions(options) {
|
|
48
|
+
this.options = Object.assign(this.options, options);
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Fetches the column information needed for building the select clauses.
|
|
54
|
+
*
|
|
55
|
+
* This must be called before calling `build(builder, true)`. `build(builder, false)`
|
|
56
|
+
* can be called without this since it doesn't build selects.
|
|
57
|
+
*/
|
|
58
|
+
fetchColumnInfo(builder) {
|
|
59
|
+
const tableTree = this._getTableTree(builder);
|
|
60
|
+
const allModelClasses = new Set(tableTree.nodes.map((node) => node.modelClass));
|
|
61
|
+
|
|
62
|
+
return mapPromise(
|
|
63
|
+
Array.from(allModelClasses),
|
|
64
|
+
(modelClass) => modelClass.fetchTableMetadata({ parentBuilder: builder }),
|
|
65
|
+
{
|
|
66
|
+
concurrency: this.rootModelClass.getConcurrency(builder.unsafeKnex()),
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
build(builder, buildSelects = true) {
|
|
72
|
+
const rootTableNode = this._getTableTree(builder).rootNode;
|
|
73
|
+
const userSelectQueries = new Map([[rootTableNode, builder]]);
|
|
74
|
+
|
|
75
|
+
for (const tableNode of rootTableNode.childNodes) {
|
|
76
|
+
this._buildJoinsForNode({ builder, tableNode, userSelectQueries });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (buildSelects) {
|
|
80
|
+
this._buildSelects({ builder, tableNode: rootTableNode, userSelectQueries });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
parseResult(builder, flatRows) {
|
|
85
|
+
const parser = JoinResultParser.create({
|
|
86
|
+
tableTree: this._getTableTree(builder),
|
|
87
|
+
omitColumnAliases: this.internalSelections.map((it) => it.alias),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return parser.parse(flatRows);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_getTableTree(builder) {
|
|
94
|
+
if (!this.tableTree) {
|
|
95
|
+
// Create the table tree lazily.
|
|
96
|
+
this.tableTree = TableTree.create({
|
|
97
|
+
rootModelClass: this.rootModelClass,
|
|
98
|
+
rootTableAlias: builder.tableRef(),
|
|
99
|
+
expression: this.expression,
|
|
100
|
+
options: this.options,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return this.tableTree;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_buildJoinsForNode({ builder, tableNode, userSelectQueries }) {
|
|
108
|
+
const subqueryToJoin = createSubqueryToJoin({
|
|
109
|
+
builder,
|
|
110
|
+
tableNode,
|
|
111
|
+
modifiers: this.modifiers,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const userSelectQuery = subqueryToJoin.clone();
|
|
115
|
+
|
|
116
|
+
// relation.join applies the relation modifier that can
|
|
117
|
+
// also contain selects.
|
|
118
|
+
userSelectQuery.modify(tableNode.relation.modify);
|
|
119
|
+
|
|
120
|
+
// Save the query that contains the user specified selects
|
|
121
|
+
// for later use.
|
|
122
|
+
userSelectQueries.set(tableNode, userSelectQuery);
|
|
123
|
+
|
|
124
|
+
tableNode.relation.join(builder, {
|
|
125
|
+
joinOperation: this.options.joinOperation,
|
|
126
|
+
|
|
127
|
+
ownerTable: tableNode.parentNode.alias,
|
|
128
|
+
relatedTableAlias: tableNode.alias,
|
|
129
|
+
joinTableAlias: tableNode.getJoinTableAlias(builder),
|
|
130
|
+
|
|
131
|
+
relatedJoinSelectQuery: ensureIdAndRelationPropsAreSelected({
|
|
132
|
+
builder: subqueryToJoin,
|
|
133
|
+
tableNode,
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
for (const childNode of tableNode.childNodes) {
|
|
138
|
+
this._buildJoinsForNode({ builder, tableNode: childNode, userSelectQueries });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_buildSelects({ builder, tableNode, userSelectQueries }) {
|
|
143
|
+
const { selections, internalSelections } = getSelectionsForNode({
|
|
144
|
+
builder,
|
|
145
|
+
tableNode,
|
|
146
|
+
userSelectQueries,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
for (const selection of selections) {
|
|
150
|
+
checkAliasLength(tableNode.modelClass, selection.name);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Save the selections that were added internally (not by the user)
|
|
154
|
+
// so that we can later remove the corresponding properties when
|
|
155
|
+
// parsing the result.
|
|
156
|
+
this.internalSelections = internalSelections;
|
|
157
|
+
|
|
158
|
+
builder.select(selectionsToStrings(selections));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function defaultOptions() {
|
|
163
|
+
return {
|
|
164
|
+
joinOperation: 'leftJoin',
|
|
165
|
+
minimize: false,
|
|
166
|
+
separator: ':',
|
|
167
|
+
aliases: {},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function createSubqueryToJoin({ builder, tableNode, modifiers }) {
|
|
172
|
+
const { relation, expression, modelClass } = tableNode;
|
|
173
|
+
const modifierQuery = modelClass.query().childQueryOf(builder);
|
|
174
|
+
|
|
175
|
+
for (const modifierName of expression.node.$modify) {
|
|
176
|
+
const modifier = createModifier({
|
|
177
|
+
modifier: modifierName,
|
|
178
|
+
modelClass,
|
|
179
|
+
modifiers,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
modifier(modifierQuery);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
if (err instanceof modelClass.ModifierNotFoundError) {
|
|
186
|
+
throw modelClass.createValidationError({
|
|
187
|
+
type: ValidationErrorType.RelationExpression,
|
|
188
|
+
message: `could not find modifier "${modifierName}" for relation "${relation.name}"`,
|
|
189
|
+
});
|
|
190
|
+
} else {
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return modifierQuery;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function ensureIdAndRelationPropsAreSelected({ builder, tableNode }) {
|
|
200
|
+
const tableRef = builder.tableRef();
|
|
201
|
+
|
|
202
|
+
const cols = [
|
|
203
|
+
...builder.modelClass().getIdColumnArray(),
|
|
204
|
+
...tableNode.relation.relatedProp.cols,
|
|
205
|
+
...tableNode.childNodes.reduce(
|
|
206
|
+
(cols, childNode) => [...cols, ...childNode.relation.ownerProp.cols],
|
|
207
|
+
[],
|
|
208
|
+
),
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
const selectStrings = uniqBy(cols)
|
|
212
|
+
.filter((col) => !builder.hasSelectionAs(col, col))
|
|
213
|
+
.map((col) => `${tableRef}.${col}`);
|
|
214
|
+
|
|
215
|
+
return builder.select(selectStrings);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function getSelectionsForNode({ builder, tableNode, userSelectQueries }) {
|
|
219
|
+
const userSelectQuery = userSelectQueries.get(tableNode);
|
|
220
|
+
const userSelections = userSelectQuery.findAllSelections();
|
|
221
|
+
const userSelectedAllColumns = isSelectAllSelectionSet(userSelections);
|
|
222
|
+
|
|
223
|
+
let selections = [];
|
|
224
|
+
let internalSelections = [];
|
|
225
|
+
|
|
226
|
+
if (tableNode.parentNode) {
|
|
227
|
+
selections = mapUserSelectionsFromSubqueryToMainQuery({ userSelections, tableNode });
|
|
228
|
+
|
|
229
|
+
if (userSelectedAllColumns && tableNode.relation.isObjectionManyToManyRelation) {
|
|
230
|
+
const extraSelections = getJoinTableExtraSelectionsForNode({ builder, tableNode });
|
|
231
|
+
selections = selections.concat(extraSelections);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (userSelectedAllColumns) {
|
|
236
|
+
const allColumnSelections = getAllColumnSelectionsForNode({ builder, tableNode });
|
|
237
|
+
selections = allColumnSelections.concat(selections);
|
|
238
|
+
} else {
|
|
239
|
+
const idSelections = getIdSelectionsForNode({ tableNode });
|
|
240
|
+
|
|
241
|
+
for (const idSelection of idSelections) {
|
|
242
|
+
if (!userSelectQuery.hasSelectionAs(idSelection.column, idSelection.column)) {
|
|
243
|
+
selections.push(idSelection);
|
|
244
|
+
internalSelections.push(idSelection);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (const childNode of tableNode.childNodes) {
|
|
250
|
+
const childResult = getSelectionsForNode({ builder, tableNode: childNode, userSelectQueries });
|
|
251
|
+
|
|
252
|
+
selections = selections.concat(childResult.selections);
|
|
253
|
+
internalSelections = internalSelections.concat(childResult.internalSelections);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
selections,
|
|
258
|
+
internalSelections,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function mapUserSelectionsFromSubqueryToMainQuery({ userSelections, tableNode }) {
|
|
263
|
+
return userSelections.filter(isNotSelectAll).map((selection) => {
|
|
264
|
+
return new Selection(
|
|
265
|
+
tableNode.alias,
|
|
266
|
+
selection.name,
|
|
267
|
+
tableNode.getColumnAliasForColumn(selection.name),
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function getJoinTableExtraSelectionsForNode({ builder, tableNode }) {
|
|
273
|
+
return tableNode.relation.joinTableExtras.map((extra) => {
|
|
274
|
+
return new Selection(
|
|
275
|
+
tableNode.getJoinTableAlias(builder),
|
|
276
|
+
extra.joinTableCol,
|
|
277
|
+
tableNode.getColumnAliasForColumn(extra.aliasCol),
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getAllColumnSelectionsForNode({ builder, tableNode }) {
|
|
283
|
+
const table = builder.tableNameFor(tableNode.modelClass);
|
|
284
|
+
const tableMeta = tableNode.modelClass.tableMetadata({ table });
|
|
285
|
+
|
|
286
|
+
if (!tableMeta) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`table metadata has not been fetched for table '${table}'. Are you trying to call toKnexQuery() for a withGraphJoined query? To make sure the table metadata is fetched see the objection.initialize function.`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return tableMeta.columns.map((columnName) => {
|
|
293
|
+
return new Selection(
|
|
294
|
+
tableNode.alias,
|
|
295
|
+
columnName,
|
|
296
|
+
tableNode.getColumnAliasForColumn(columnName),
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function getIdSelectionsForNode({ tableNode }) {
|
|
302
|
+
return tableNode.modelClass.getIdColumnArray().map((columnName) => {
|
|
303
|
+
return new Selection(
|
|
304
|
+
tableNode.alias,
|
|
305
|
+
columnName,
|
|
306
|
+
tableNode.getColumnAliasForColumn(columnName),
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function selectionsToStrings(selections) {
|
|
312
|
+
return selections.map((selection) => {
|
|
313
|
+
const selectStr = `${selection.table}.${selection.column}`;
|
|
314
|
+
return `${selectStr} as ${selection.alias}`;
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function isSelectAll(selection) {
|
|
319
|
+
return selection.column === '*';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function isNotSelectAll(selection) {
|
|
323
|
+
return selection.column !== '*';
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function isSelectAllSelectionSet(selections) {
|
|
327
|
+
return selections.length === 0 || selections.some(isSelectAll);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function checkAliasLength(modelClass, alias) {
|
|
331
|
+
if (alias.length > ID_LENGTH_LIMIT) {
|
|
332
|
+
throw modelClass.createValidationError({
|
|
333
|
+
type: ValidationErrorType.RelationExpression,
|
|
334
|
+
message: `identifier ${alias} is over ${ID_LENGTH_LIMIT} characters long and would be truncated by the database engine.`,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
module.exports = {
|
|
340
|
+
RelationJoiner,
|
|
341
|
+
};
|