cruddl 5.0.0-alpha.1 → 5.0.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/.github/workflows/ci.yml +7 -4
- package/dist/src/cruddl-version.js +1 -1
- package/dist/src/database/arangodb/aql-generator.d.ts +1 -1
- package/dist/src/database/arangodb/aql-generator.js +228 -65
- package/dist/src/database/arangodb/aql-generator.js.map +1 -1
- package/dist/src/database/arangodb/schema-migration/arango-search-helpers.js +17 -3
- package/dist/src/database/arangodb/schema-migration/arango-search-helpers.js.map +1 -1
- package/dist/src/database/arangodb/traversal-helpers.d.ts +11 -8
- package/dist/src/database/arangodb/traversal-helpers.js +67 -24
- package/dist/src/database/arangodb/traversal-helpers.js.map +1 -1
- package/dist/src/database/inmemory/js-generator.js +6 -1
- package/dist/src/database/inmemory/js-generator.js.map +1 -1
- package/dist/src/query-tree/queries.js +2 -2
- package/dist/src/query-tree/queries.js.map +1 -1
- package/dist/src/query-tree/utils/extract-variable-assignments.d.ts +6 -10
- package/dist/src/query-tree/utils/extract-variable-assignments.js +33 -28
- package/dist/src/query-tree/utils/extract-variable-assignments.js.map +1 -1
- package/dist/src/query-tree/utils/index.d.ts +1 -0
- package/dist/src/query-tree/utils/index.js +1 -0
- package/dist/src/query-tree/utils/index.js.map +1 -1
- package/dist/src/query-tree/utils/referenced-variables.d.ts +3 -0
- package/dist/src/query-tree/utils/referenced-variables.js +18 -0
- package/dist/src/query-tree/utils/referenced-variables.js.map +1 -0
- package/dist/src/query-tree/variables.d.ts +19 -0
- package/dist/src/query-tree/variables.js +24 -1
- package/dist/src/query-tree/variables.js.map +1 -1
- package/dist/src/schema-generation/field-nodes.d.ts +12 -0
- package/dist/src/schema-generation/field-nodes.js +21 -3
- package/dist/src/schema-generation/field-nodes.js.map +1 -1
- package/dist/src/schema-generation/output-type-generator.js +4 -0
- package/dist/src/schema-generation/output-type-generator.js.map +1 -1
- package/dist/src/schema-generation/query-node-object-type/definition.d.ts +5 -0
- package/dist/src/schema-generation/query-node-object-type/definition.js.map +1 -1
- package/dist/src/schema-generation/query-node-object-type/query-node-generator.js +11 -6
- package/dist/src/schema-generation/query-node-object-type/query-node-generator.js.map +1 -1
- package/dist/src/utils/visitor.d.ts +10 -0
- package/dist/src/utils/visitor.js.map +1 -1
- package/package.json +1 -1
package/.github/workflows/ci.yml
CHANGED
|
@@ -5,7 +5,7 @@ jobs:
|
|
|
5
5
|
runs-on: ubuntu-latest
|
|
6
6
|
strategy:
|
|
7
7
|
matrix:
|
|
8
|
-
arango-image: ['arangodb:3.12']
|
|
8
|
+
arango-image: ['arangodb:3.12', 'arangodb:3.12.6']
|
|
9
9
|
node-version: [20.x, 22.x, 24.x, 25.x]
|
|
10
10
|
steps:
|
|
11
11
|
- uses: actions/checkout@v2
|
|
@@ -26,11 +26,14 @@ jobs:
|
|
|
26
26
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
27
27
|
runs-on: ubuntu-latest
|
|
28
28
|
needs: test
|
|
29
|
+
permissions:
|
|
30
|
+
id-token: write
|
|
31
|
+
contents: read
|
|
29
32
|
steps:
|
|
30
|
-
- uses: actions/checkout@
|
|
31
|
-
- uses: actions/setup-node@
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
- uses: actions/setup-node@v4
|
|
32
35
|
with:
|
|
33
|
-
node-version:
|
|
36
|
+
node-version: '24'
|
|
34
37
|
registry-url: https://registry.npmjs.org/
|
|
35
38
|
- run: npm ci
|
|
36
39
|
- run: npm publish --tag next
|
|
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CRUDDL_VERSION = void 0;
|
|
4
4
|
// do not modify - this is parsed and changed by the build script
|
|
5
5
|
// explicitly annotating with "string" so typescript won't infer a string literal type
|
|
6
|
-
exports.CRUDDL_VERSION = '5.0.0
|
|
6
|
+
exports.CRUDDL_VERSION = '5.0.0';
|
|
7
7
|
//# sourceMappingURL=cruddl-version.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { Clock, IDGenerator } from '../../execution/execution-options';
|
|
1
2
|
import { QueryNode } from '../../query-tree';
|
|
2
3
|
import { FlexSearchTokenizable } from '../database-adapter';
|
|
3
4
|
import { AQLCompoundQuery, AQLFragment } from './aql';
|
|
4
|
-
import { Clock, IDGenerator } from '../../execution/execution-options';
|
|
5
5
|
export interface QueryGenerationOptions {
|
|
6
6
|
/**
|
|
7
7
|
* An interface to determine the current date/time
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getAQLQuery = getAQLQuery;
|
|
4
4
|
exports.generateTokenizationQuery = generateTokenizationQuery;
|
|
5
|
+
const execution_options_1 = require("../../execution/execution-options");
|
|
5
6
|
const model_1 = require("../../model");
|
|
6
7
|
const flex_search_1 = require("../../model/implementation/flex-search");
|
|
7
8
|
const query_tree_1 = require("../../query-tree");
|
|
@@ -14,7 +15,6 @@ const like_helpers_1 = require("../like-helpers");
|
|
|
14
15
|
const aql_1 = require("./aql");
|
|
15
16
|
const arango_basics_1 = require("./arango-basics");
|
|
16
17
|
const arango_search_helpers_1 = require("./schema-migration/arango-search-helpers");
|
|
17
|
-
const execution_options_1 = require("../../execution/execution-options");
|
|
18
18
|
const traversal_helpers_1 = require("./traversal-helpers");
|
|
19
19
|
var AccessType;
|
|
20
20
|
(function (AccessType) {
|
|
@@ -299,6 +299,10 @@ register(query_tree_1.ConcatListsQueryNode, (node, context) => {
|
|
|
299
299
|
register(query_tree_1.VariableQueryNode, (node, context) => {
|
|
300
300
|
return context.getVariable(node);
|
|
301
301
|
});
|
|
302
|
+
register(query_tree_1.HoistableQueryNode, (node, context) => {
|
|
303
|
+
// if we process a HoistableQueryNode here, the node did not get hoisted, but that's fine too
|
|
304
|
+
return processNode(node.node, context);
|
|
305
|
+
});
|
|
302
306
|
register(query_tree_1.VariableAssignmentQueryNode, (node, context) => {
|
|
303
307
|
const newContext = context.bindVariable(node.variableNode);
|
|
304
308
|
const tmpVar = newContext.getVariable(node.variableNode);
|
|
@@ -367,27 +371,60 @@ register(flex_search_2.FlexSearchQueryNode, (node, context) => {
|
|
|
367
371
|
return aqlExt.subquery((0, aql_1.aql) `FOR ${itemContext.getVariable(node.itemVariable)}`, (0, aql_1.aql) `IN ${aql_1.aql.collection(viewName)}`, (0, aql_1.aql) `SEARCH ${processNode(node.flexFilterNode, itemContext)}`, node.isOptimisationsDisabled ? (0, aql_1.aql) `OPTIONS { conditionOptimization: 'none' }` : (0, aql_1.aql) ``, (0, aql_1.aql) `RETURN ${itemContext.getVariable(node.itemVariable)}`);
|
|
368
372
|
});
|
|
369
373
|
register(query_tree_1.TransformListQueryNode, (node, context) => {
|
|
370
|
-
let itemContext = context.bindVariable(node.itemVariable);
|
|
371
|
-
const itemVar = itemContext.getVariable(node.itemVariable);
|
|
372
|
-
let itemProjectionContext = itemContext;
|
|
373
374
|
// move LET statements up
|
|
374
375
|
// they often occur for value objects / entity extensions
|
|
375
376
|
// this avoids the FIRST() and the subquery which reduces load on the AQL query optimizer
|
|
376
|
-
|
|
377
|
+
const hoistedAssignments = [];
|
|
378
|
+
const loopScopedAssignmentNodes = [];
|
|
379
|
+
const loopScopedVariables = new Set();
|
|
380
|
+
let currentContext = context;
|
|
377
381
|
let innerNode = node.innerNode;
|
|
378
382
|
const variableAssignmentNodes = [];
|
|
379
383
|
innerNode = (0, utils_1.extractVariableAssignments)(innerNode, variableAssignmentNodes);
|
|
380
384
|
for (const assignmentNode of variableAssignmentNodes) {
|
|
385
|
+
const referencedVariables = (0, utils_1.getReferencedVariables)(assignmentNode.variableValueNode);
|
|
386
|
+
const referencesItemVariable = referencedVariables.has(node.itemVariable);
|
|
387
|
+
let referencesLoopScopedVariable = false;
|
|
388
|
+
for (const variableNode of loopScopedVariables) {
|
|
389
|
+
if (referencedVariables.has(variableNode)) {
|
|
390
|
+
referencesLoopScopedVariable = true;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (!referencesItemVariable && !referencesLoopScopedVariable) {
|
|
395
|
+
currentContext = currentContext.bindVariable(assignmentNode.variableNode);
|
|
396
|
+
const tmpVar = currentContext.getVariable(assignmentNode.variableNode);
|
|
397
|
+
// ArangoDB will try to move the hoisted variable down to their usages again, even
|
|
398
|
+
// though that increases memory usage because it requires the subquery to hold the whole
|
|
399
|
+
// source (root) variable. NOEVAL() forces the optimizer to keep the variable where it's
|
|
400
|
+
// declared
|
|
401
|
+
hoistedAssignments.push((0, aql_1.aql) `LET ${tmpVar} = NOEVAL(${processNode(assignmentNode.variableValueNode, currentContext)})`);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
loopScopedAssignmentNodes.push(assignmentNode);
|
|
405
|
+
loopScopedVariables.add(assignmentNode.variableNode);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
let itemContext = currentContext.bindVariable(node.itemVariable);
|
|
409
|
+
const itemVar = itemContext.getVariable(node.itemVariable);
|
|
410
|
+
let itemProjectionContext = itemContext;
|
|
411
|
+
const loopScopedAssignments = [];
|
|
412
|
+
for (const assignmentNode of loopScopedAssignmentNodes) {
|
|
381
413
|
itemProjectionContext = itemProjectionContext.bindVariable(assignmentNode.variableNode);
|
|
382
414
|
const tmpVar = itemProjectionContext.getVariable(assignmentNode.variableNode);
|
|
383
|
-
|
|
415
|
+
loopScopedAssignments.push((0, aql_1.aql) `LET ${tmpVar} = ${processNode(assignmentNode.variableValueNode, itemProjectionContext)}`);
|
|
384
416
|
}
|
|
385
417
|
// TODO aql-perf: set maxProjects to a value > 5?
|
|
386
418
|
// The reduce-extraction-to-projection optimization is crucial to reduce memory usage of queries
|
|
387
419
|
// over large root entities if only some of the fields are queried. The default is to only apply
|
|
388
420
|
// it if 5 or less fields are selected. We probably want to increase this limit
|
|
389
421
|
// (also applies to FollowEdgeQueryNode and TraversalQueryNode)
|
|
390
|
-
return aqlExt.subquery((0, aql_1.aql) `FOR ${itemVar}`, generateInClauseWithFilterAndOrderAndLimit({
|
|
422
|
+
return aqlExt.subquery(...hoistedAssignments, (0, aql_1.aql) `FOR ${itemVar}`, generateInClauseWithFilterAndOrderAndLimit({
|
|
423
|
+
node,
|
|
424
|
+
context: currentContext,
|
|
425
|
+
itemContext,
|
|
426
|
+
itemVar,
|
|
427
|
+
}), ...loopScopedAssignments, (0, aql_1.aql) `RETURN ${processNode(innerNode, itemProjectionContext)}`);
|
|
391
428
|
});
|
|
392
429
|
/**
|
|
393
430
|
* Generates an IN... clause for a TransformListQueryNode to be used within a query / subquery (FOR ... IN ...)
|
|
@@ -1060,7 +1097,7 @@ register(query_tree_1.TraversalQueryNode, (node, context) => {
|
|
|
1060
1097
|
return processTraversalWithNonListRelationSegmentsAndNonListFieldSegments(node, context);
|
|
1061
1098
|
}
|
|
1062
1099
|
}
|
|
1063
|
-
if (
|
|
1100
|
+
if (!(0, traversal_helpers_1.supportedAsArrayExpansion)(node, { skipTopLevelChecks: true })) {
|
|
1064
1101
|
// cannot have subqueries within array expansions, so we need to use a subquery here
|
|
1065
1102
|
return processTraversalWithRelationAndListFieldSegmentsUsingSubquery(node, context);
|
|
1066
1103
|
}
|
|
@@ -1087,8 +1124,7 @@ register(query_tree_1.TraversalQueryNode, (node, context) => {
|
|
|
1087
1124
|
// In the simple case, we can use an array expansion expression instead of a subquery
|
|
1088
1125
|
// - SORT is not supported by array expressions (documented)
|
|
1089
1126
|
// - subqueries in array expressions currently cause an internal error in arangodb (3.12.6)
|
|
1090
|
-
if (
|
|
1091
|
-
(!node.innerNode || !(0, traversal_helpers_1.mightGenerateSubquery)(node.innerNode))) {
|
|
1127
|
+
if ((0, traversal_helpers_1.supportedAsArrayExpansion)(node)) {
|
|
1092
1128
|
return processTraversalWithOnlyFieldSegmentsUsingArrayExpansion(node, context);
|
|
1093
1129
|
}
|
|
1094
1130
|
else {
|
|
@@ -1100,14 +1136,109 @@ register(query_tree_1.TraversalQueryNode, (node, context) => {
|
|
|
1100
1136
|
throw new Error(`TraversalQueryNode must have at least one segment`);
|
|
1101
1137
|
}
|
|
1102
1138
|
});
|
|
1139
|
+
/**
|
|
1140
|
+
* Extracts three kinds of VariableAssignmentQueryNode from the traversal's innerNode
|
|
1141
|
+
*
|
|
1142
|
+
* Variables that depend on nested traversals etc. are not extracted.
|
|
1143
|
+
*/
|
|
1144
|
+
function extractTraversalAssignments(node) {
|
|
1145
|
+
if (!node.innerNode) {
|
|
1146
|
+
return {
|
|
1147
|
+
innerNode: undefined,
|
|
1148
|
+
traversalIndependentAssignments: [],
|
|
1149
|
+
rootScopedAssignments: [],
|
|
1150
|
+
itemScopedAssignments: [],
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
const extractedAssignments = [];
|
|
1154
|
+
const processedInnerNode = (0, utils_1.extractVariableAssignments)(node.innerNode, extractedAssignments);
|
|
1155
|
+
const traversalIndependentAssignments = [];
|
|
1156
|
+
const rootScopedAssignments = [];
|
|
1157
|
+
const itemScopedAssignments = [];
|
|
1158
|
+
const rootScopedVariables = [node.rootEntityVariable];
|
|
1159
|
+
const itemScopedVariables = [node.itemVariable];
|
|
1160
|
+
for (const assignmentNode of extractedAssignments) {
|
|
1161
|
+
const referencedVariables = (0, utils_1.getReferencedVariables)(assignmentNode.variableValueNode);
|
|
1162
|
+
if (itemScopedVariables.some((v) => referencedVariables.has(v))) {
|
|
1163
|
+
itemScopedAssignments.push(assignmentNode);
|
|
1164
|
+
itemScopedVariables.push(assignmentNode.variableNode);
|
|
1165
|
+
}
|
|
1166
|
+
else if (rootScopedVariables.some((v) => referencedVariables.has(v))) {
|
|
1167
|
+
rootScopedAssignments.push(assignmentNode);
|
|
1168
|
+
rootScopedVariables.push(assignmentNode.variableNode);
|
|
1169
|
+
}
|
|
1170
|
+
else {
|
|
1171
|
+
traversalIndependentAssignments.push(assignmentNode);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return {
|
|
1175
|
+
innerNode: processedInnerNode,
|
|
1176
|
+
traversalIndependentAssignments,
|
|
1177
|
+
rootScopedAssignments,
|
|
1178
|
+
itemScopedAssignments,
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Extracts variable assignments from the traversal's innerNode and produces LET statements
|
|
1183
|
+
*/
|
|
1184
|
+
function extractTraversalAssignmentsAsAql({ node, context, rootVar, itemVar, wrapRootVarsInNoEval = true, }) {
|
|
1185
|
+
// TODO aql-perf: can there also be VariableAssignments in filterNode? If yes, should we hoist them?
|
|
1186
|
+
const { innerNode: processedInnerNode, traversalIndependentAssignments, rootScopedAssignments, itemScopedAssignments, } = extractTraversalAssignments(node);
|
|
1187
|
+
if (rootScopedAssignments.length > 0 && !rootVar) {
|
|
1188
|
+
throw new Error('Found variable assignments that depend on the root variable, but the current traversal variant does not support them.');
|
|
1189
|
+
}
|
|
1190
|
+
if (itemScopedAssignments.length > 0 && !itemVar) {
|
|
1191
|
+
throw new Error('Found variable assignments that depend on the loop variable, but the current traversal variant does not support them.');
|
|
1192
|
+
}
|
|
1193
|
+
const { fragments: independentAssignmentFrags, context: baseContext } = buildAssignmentFragments(traversalIndependentAssignments, context, {
|
|
1194
|
+
wrapWithNoEval: true,
|
|
1195
|
+
});
|
|
1196
|
+
const rootBaseContext = rootVar
|
|
1197
|
+
? baseContext.bindVariable(node.rootEntityVariable, rootVar)
|
|
1198
|
+
: baseContext;
|
|
1199
|
+
const { fragments: rootAssignmentFrags, context: rootContext } = buildAssignmentFragments(rootScopedAssignments, rootBaseContext, {
|
|
1200
|
+
wrapWithNoEval: wrapRootVarsInNoEval,
|
|
1201
|
+
});
|
|
1202
|
+
const itemBaseContext = itemVar
|
|
1203
|
+
? rootContext.bindVariable(node.itemVariable, itemVar)
|
|
1204
|
+
: rootContext;
|
|
1205
|
+
const { fragments: itemAssignmentFrags, context: itemContext } = buildAssignmentFragments(itemScopedAssignments, itemBaseContext);
|
|
1206
|
+
const innerFrag = itemVar && processedInnerNode
|
|
1207
|
+
? processNode(processedInnerNode, itemContext)
|
|
1208
|
+
: (itemVar ?? (0, aql_1.aql) `NULL`);
|
|
1209
|
+
return {
|
|
1210
|
+
processedInnerNode,
|
|
1211
|
+
innerFrag,
|
|
1212
|
+
independentAssignmentFrags,
|
|
1213
|
+
rootAssignmentFrags,
|
|
1214
|
+
itemAssignmentFrags,
|
|
1215
|
+
rootContext,
|
|
1216
|
+
itemBaseContext,
|
|
1217
|
+
itemContext,
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
function buildAssignmentFragments(assignments, startContext, { wrapWithNoEval = false } = {}) {
|
|
1221
|
+
let currentContext = startContext;
|
|
1222
|
+
const fragments = [];
|
|
1223
|
+
for (const assignmentNode of assignments) {
|
|
1224
|
+
currentContext = currentContext.bindVariable(assignmentNode.variableNode);
|
|
1225
|
+
const tmpVar = currentContext.getVariable(assignmentNode.variableNode);
|
|
1226
|
+
const valueFrag = processNode(assignmentNode.variableValueNode, currentContext);
|
|
1227
|
+
fragments.push(wrapWithNoEval
|
|
1228
|
+
? (0, aql_1.aql) `LET ${tmpVar} = NOEVAL(${valueFrag})`
|
|
1229
|
+
: (0, aql_1.aql) `LET ${tmpVar} = ${valueFrag}`);
|
|
1230
|
+
}
|
|
1231
|
+
return { fragments, context: currentContext };
|
|
1232
|
+
}
|
|
1103
1233
|
/**
|
|
1104
1234
|
* Produces:
|
|
1105
1235
|
*
|
|
1106
1236
|
* FIRST(
|
|
1107
1237
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1108
1238
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1109
|
-
* FOR
|
|
1110
|
-
*
|
|
1239
|
+
* FOR item IN OUTBOUND hop2_target
|
|
1240
|
+
* LET extractedVar = variableValueExpr(item)
|
|
1241
|
+
* RETURN innerNode(item)
|
|
1111
1242
|
* )
|
|
1112
1243
|
*
|
|
1113
1244
|
* or, if preserveNullValues is true and hop2_target is a to-1 relation:
|
|
@@ -1115,8 +1246,9 @@ register(query_tree_1.TraversalQueryNode, (node, context) => {
|
|
|
1115
1246
|
* FIRST(
|
|
1116
1247
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1117
1248
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1118
|
-
* LET
|
|
1119
|
-
*
|
|
1249
|
+
* LET item = FIRST(FOR node3 IN OUTBOUND hop2_target RETURNN node3)
|
|
1250
|
+
* LET extractedVar = variableValueExpr(item)
|
|
1251
|
+
* RETURN innerNode(item)
|
|
1120
1252
|
* )
|
|
1121
1253
|
*/
|
|
1122
1254
|
function processTraversalWithOnlyRelationSegmentsNoList(node, context) {
|
|
@@ -1129,15 +1261,15 @@ function processTraversalWithOnlyRelationSegmentsNoList(node, context) {
|
|
|
1129
1261
|
node.maxCount !== undefined) {
|
|
1130
1262
|
throw new Error(`Cannot have filter, orderBy, skip or maxCount on non-list relation traversal`);
|
|
1131
1263
|
}
|
|
1132
|
-
const
|
|
1133
|
-
const itemVar = innerContext.getVariable(node.itemVariable);
|
|
1264
|
+
const itemVar = aql_1.aql.variable(node.itemVariable.label ?? 'item');
|
|
1134
1265
|
const forStatementsFrag = getRelationTraversalForStatements({
|
|
1135
1266
|
node,
|
|
1136
1267
|
innermostItemVar: itemVar,
|
|
1137
1268
|
preserveNullValues: node.preserveNullValues,
|
|
1138
1269
|
context,
|
|
1139
1270
|
});
|
|
1140
|
-
|
|
1271
|
+
const { independentAssignmentFrags, itemAssignmentFrags, innerFrag } = extractTraversalAssignmentsAsAql({ node, context, itemVar });
|
|
1272
|
+
return aqlExt.firstOfSubquery(...independentAssignmentFrags, forStatementsFrag, ...itemAssignmentFrags, (0, aql_1.aql) `RETURN ${innerFrag}`);
|
|
1141
1273
|
}
|
|
1142
1274
|
/**
|
|
1143
1275
|
* Produces:
|
|
@@ -1145,6 +1277,7 @@ function processTraversalWithOnlyRelationSegmentsNoList(node, context) {
|
|
|
1145
1277
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1146
1278
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1147
1279
|
* FOR item IN OUTBOUND hop2_target
|
|
1280
|
+
* LET extractedVar = variableValueExpr(item)
|
|
1148
1281
|
* FILTER filterExpr(item)
|
|
1149
1282
|
* SORT item.field1 ASC
|
|
1150
1283
|
* LIMIT skip, maxCount
|
|
@@ -1155,6 +1288,7 @@ function processTraversalWithOnlyRelationSegmentsNoList(node, context) {
|
|
|
1155
1288
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1156
1289
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1157
1290
|
* LET item = FIRST(FOR node3 IN OUTBOUND hop2_target RETURN node3)
|
|
1291
|
+
* LET extractedVar = variableValueExpr(item)
|
|
1158
1292
|
* FILTER filterExpr(item)
|
|
1159
1293
|
* SORT item.field1 ASC
|
|
1160
1294
|
* LIMIT skip, maxCount
|
|
@@ -1170,39 +1304,43 @@ function processTraversalWithOnlyRelationSegmentsAsList(node, context) {
|
|
|
1170
1304
|
// - alwaysProduceList is automatically handled because we always RETURN a list here
|
|
1171
1305
|
// we could refactor the single usage so it does not use a TraversalQueryNode in the first place
|
|
1172
1306
|
const itemVar = aql_1.aql.variable(`node`);
|
|
1173
|
-
const innerContext = context.bindVariable(node.itemVariable, itemVar);
|
|
1174
1307
|
const forStatementsFrag = getRelationTraversalForStatements({
|
|
1175
1308
|
node,
|
|
1176
1309
|
innermostItemVar: itemVar,
|
|
1177
1310
|
context,
|
|
1178
1311
|
preserveNullValues: node.preserveNullValues,
|
|
1179
1312
|
});
|
|
1180
|
-
|
|
1313
|
+
const { independentAssignmentFrags, itemAssignmentFrags, itemBaseContext, innerFrag } = extractTraversalAssignmentsAsAql({ node, context, itemVar });
|
|
1314
|
+
return aqlExt.subquery(...independentAssignmentFrags, forStatementsFrag, node.filterNode ? (0, aql_1.aql) `FILTER ${processNode(node.filterNode, itemBaseContext)}` : (0, aql_1.aql) ``,
|
|
1181
1315
|
// yes, we can SORT and LIMIT like this even if there are multiple FOR statements
|
|
1182
1316
|
// because there is one result set for the cross product of all FOR statements
|
|
1183
1317
|
// see https://docs.arangodb.com/3.12/aql/high-level-operations/for/#usage
|
|
1184
|
-
generateSortAQL(node.orderBy,
|
|
1318
|
+
generateSortAQL(node.orderBy, itemBaseContext), generateLimitClause(node) ?? (0, aql_1.aql) ``, ...itemAssignmentFrags, (0, aql_1.aql) `RETURN ${innerFrag}`);
|
|
1185
1319
|
}
|
|
1186
1320
|
/**
|
|
1187
1321
|
* Produces:
|
|
1188
1322
|
*
|
|
1189
1323
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1190
1324
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1191
|
-
* FOR
|
|
1192
|
-
*
|
|
1193
|
-
*
|
|
1325
|
+
* FOR root IN OUTBOUND hop2_target
|
|
1326
|
+
* LET extractedVar1 = variable1ValueExpr(root))
|
|
1327
|
+
* LET extractedVar2 = variable2ValueExpr(root.fieldSegment1.fieldSegment2)
|
|
1328
|
+
* FILTER filterExpr(root.fieldSegment1.fieldSegment2)
|
|
1329
|
+
* SORT root.fieldSegment1.fieldSegment2.sortField ASC
|
|
1194
1330
|
* LIMIT skip, maxCount
|
|
1195
|
-
* RETURN innerNode(
|
|
1331
|
+
* RETURN innerNode(root.fieldSegment1.fieldSegment2, { root: node2 })
|
|
1196
1332
|
*
|
|
1197
1333
|
* or, if preserveNullValues is true and hop2_target is a to-1 relation:
|
|
1198
1334
|
*
|
|
1199
1335
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1200
1336
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1201
|
-
* LET
|
|
1202
|
-
*
|
|
1203
|
-
*
|
|
1337
|
+
* LET root = FIRST(FOR node3 IN OUTBOUND hop2_target RETURN node3)
|
|
1338
|
+
* LET extractedVar1 = variable1ValueExpr(root)
|
|
1339
|
+
* LET extractedVar2 = variable2ValueExpr(root.fieldSegment1.fieldSegment2)
|
|
1340
|
+
* FILTER filterExpr(root.fieldSegment1.fieldSegment2)
|
|
1341
|
+
* SORT root.fieldSegment1.fieldSegment2.sortField ASC
|
|
1204
1342
|
* LIMIT skip, maxCount
|
|
1205
|
-
* RETURN innerNode(
|
|
1343
|
+
* RETURN innerNode(root.fieldSegment1.fieldSegment2, { root: node2 })
|
|
1206
1344
|
*/
|
|
1207
1345
|
function processTraversalWithListRelationSegmentsAndNonListFieldSegments(node, context) {
|
|
1208
1346
|
if (!node.relationSegments.some((f) => f.isListSegment)) {
|
|
@@ -1224,11 +1362,19 @@ function processTraversalWithListRelationSegmentsAndNonListFieldSegments(node, c
|
|
|
1224
1362
|
segments: node.fieldSegments,
|
|
1225
1363
|
sourceFrag: rootVar,
|
|
1226
1364
|
});
|
|
1227
|
-
const
|
|
1365
|
+
const { independentAssignmentFrags, itemBaseContext, itemAssignmentFrags, rootAssignmentFrags, innerFrag, } = extractTraversalAssignmentsAsAql({
|
|
1366
|
+
node,
|
|
1367
|
+
context,
|
|
1368
|
+
rootVar,
|
|
1369
|
+
itemVar: fieldTraversalFrag,
|
|
1370
|
+
// there is no loop within the context of a root item where the root var assignment could
|
|
1371
|
+
// be pushed down into, so we don't need to guard against that
|
|
1372
|
+
wrapRootVarsInNoEval: false,
|
|
1373
|
+
});
|
|
1228
1374
|
// note: we don't filter out NULL values even if preserveNullValues is false because that's currently
|
|
1229
1375
|
// only a flag for performance - actually filtering out NULLs is done by a surrounding AggregationQueryNode
|
|
1230
1376
|
// TODO aql-perf remove this note once the NULL filtering / preserving has moved out of AggregationQueryNode
|
|
1231
|
-
return aqlExt.subquery(
|
|
1377
|
+
return aqlExt.subquery(...independentAssignmentFrags, (0, aql_1.aql) `${forStatementsFrag}`, node.filterNode ? (0, aql_1.aql) `FILTER ${processNode(node.filterNode, itemBaseContext)}` : (0, aql_1.aql) ``, generateSortAQL(node.orderBy, itemBaseContext), generateLimitClause(node) ?? (0, aql_1.aql) ``, ...rootAssignmentFrags, ...itemAssignmentFrags, (0, aql_1.aql) `RETURN ${innerFrag}`);
|
|
1232
1378
|
}
|
|
1233
1379
|
/**
|
|
1234
1380
|
* Produces:
|
|
@@ -1236,8 +1382,10 @@ function processTraversalWithListRelationSegmentsAndNonListFieldSegments(node, c
|
|
|
1236
1382
|
* FIRST(
|
|
1237
1383
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1238
1384
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1239
|
-
* FOR
|
|
1240
|
-
*
|
|
1385
|
+
* FOR root IN OUTBOUND hop2_target
|
|
1386
|
+
* LET extractedVar1 = variable1ValueExpr(root)
|
|
1387
|
+
* LET extractedVar2 = variable2ValueExpr(root.fieldSegment1.fieldSegment2)
|
|
1388
|
+
* RETURN innerNode(root.fieldSegment1.fieldSegment2, { root: node2 })
|
|
1241
1389
|
* )
|
|
1242
1390
|
*
|
|
1243
1391
|
* or, if preserveNullValues is true and hop2_target is a to-1 relation:
|
|
@@ -1245,8 +1393,10 @@ function processTraversalWithListRelationSegmentsAndNonListFieldSegments(node, c
|
|
|
1245
1393
|
* FIRST(
|
|
1246
1394
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1247
1395
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1248
|
-
* LET
|
|
1249
|
-
*
|
|
1396
|
+
* LET root = FIRST(FOR node3 IN OUTBOUND hop2_target RETURN node3)
|
|
1397
|
+
* LET extractedVar1 = variable1ValueExpr(root)
|
|
1398
|
+
* LET extractedVar2 = variable2ValueExpr(root.fieldSegment1.fieldSegment2)
|
|
1399
|
+
* RETURN innerNode(root.fieldSegment1.fieldSegment2, { root: node2 })
|
|
1250
1400
|
* )
|
|
1251
1401
|
*/
|
|
1252
1402
|
function processTraversalWithNonListRelationSegmentsAndNonListFieldSegments(node, context) {
|
|
@@ -1264,7 +1414,7 @@ function processTraversalWithNonListRelationSegmentsAndNonListFieldSegments(node
|
|
|
1264
1414
|
}
|
|
1265
1415
|
// this is very similar to processTraversalWithOnlyRelationSegmentsNoList(),
|
|
1266
1416
|
// but instead of using the rootVar in mapping, we use the field traversal result
|
|
1267
|
-
const rootVar = aql_1.aql.variable(
|
|
1417
|
+
const rootVar = aql_1.aql.variable('root');
|
|
1268
1418
|
const forStatementsFrag = getRelationTraversalForStatements({
|
|
1269
1419
|
node,
|
|
1270
1420
|
innermostItemVar: rootVar,
|
|
@@ -1275,8 +1425,16 @@ function processTraversalWithNonListRelationSegmentsAndNonListFieldSegments(node
|
|
|
1275
1425
|
segments: node.fieldSegments,
|
|
1276
1426
|
sourceFrag: rootVar,
|
|
1277
1427
|
});
|
|
1278
|
-
const
|
|
1279
|
-
|
|
1428
|
+
const { independentAssignmentFrags, itemAssignmentFrags, rootAssignmentFrags, innerFrag } = extractTraversalAssignmentsAsAql({
|
|
1429
|
+
node,
|
|
1430
|
+
context,
|
|
1431
|
+
rootVar,
|
|
1432
|
+
itemVar: fieldTraversalFrag,
|
|
1433
|
+
// there is no loop within the context of a root item where the root var assignment could
|
|
1434
|
+
// be pushed down into, so we don't need to guard against that
|
|
1435
|
+
wrapRootVarsInNoEval: false,
|
|
1436
|
+
});
|
|
1437
|
+
return aqlExt.firstOfSubquery(...independentAssignmentFrags, (0, aql_1.aql) `${forStatementsFrag}`, ...rootAssignmentFrags, ...itemAssignmentFrags, (0, aql_1.aql) `RETURN ${innerFrag}`);
|
|
1280
1438
|
}
|
|
1281
1439
|
/**
|
|
1282
1440
|
* Produces:
|
|
@@ -1284,7 +1442,9 @@ function processTraversalWithNonListRelationSegmentsAndNonListFieldSegments(node
|
|
|
1284
1442
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1285
1443
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1286
1444
|
* FOR root IN OUTBOUND hop2_target
|
|
1445
|
+
* LET extractedVar1 = NOEVAL(variable1ValueExpr(root))
|
|
1287
1446
|
* FOR item IN root.fieldSegment1[*].fieldSegment2[** FILTER filterExpr(CURRENT)][*]
|
|
1447
|
+
* LET extractedVar2 = variable1ValueExpr(item)
|
|
1288
1448
|
* SORT item.sortValues[0]
|
|
1289
1449
|
* LIMIT skip, maxCount
|
|
1290
1450
|
* RETURN innerNode(item, { root })
|
|
@@ -1294,7 +1454,9 @@ function processTraversalWithNonListRelationSegmentsAndNonListFieldSegments(node
|
|
|
1294
1454
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1295
1455
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1296
1456
|
* LET root = FIRST(FOR node3 IN OUTBOUND hop2_target RETURN node3)
|
|
1457
|
+
* LET extractedVar1 = NOEVAL(variable1ValueExpr(root))
|
|
1297
1458
|
* FOR item IN root.fieldSegment1[*].fieldSegment2[** FILTER filterExpr(CURRENT)][*]
|
|
1459
|
+
* LET extractedVar2 = variable1ValueExpr(item)
|
|
1298
1460
|
* SORT item.sortValues[0]
|
|
1299
1461
|
* LIMIT skip, maxCount
|
|
1300
1462
|
* RETURN innerNode(item, { root })
|
|
@@ -1331,13 +1493,11 @@ function processTraversalWithRelationAndListFieldSegmentsUsingSubquery(node, con
|
|
|
1331
1493
|
sourceFrag: rootVar,
|
|
1332
1494
|
filterFrag: innerFilterFrag,
|
|
1333
1495
|
});
|
|
1334
|
-
const itemVar = aql_1.aql.variable(
|
|
1335
|
-
const
|
|
1336
|
-
.bindVariable(node.itemVariable, itemVar)
|
|
1337
|
-
.bindVariable(node.rootEntityVariable, rootVar);
|
|
1496
|
+
const itemVar = aql_1.aql.variable(node.itemVariable.label ?? 'item');
|
|
1497
|
+
const { independentAssignmentFrags, rootAssignmentFrags, itemAssignmentFrags, itemBaseContext, innerFrag, } = extractTraversalAssignmentsAsAql({ node, context, rootVar, itemVar });
|
|
1338
1498
|
// The fieldTraversalFrag will produce a list, so a simple RETURN mapFrag would result in nested lists
|
|
1339
1499
|
// -> we iterate over the items again to flatten the lists (the FOR ${itemVar} ...)
|
|
1340
|
-
return aqlExt.subquery((0, aql_1.aql) `${forStatementsFrag}`, (0, aql_1.aql) `FOR ${itemVar} IN ${fieldTraversalFrag}`, generateSortAQL(node.orderBy,
|
|
1500
|
+
return aqlExt.subquery(...independentAssignmentFrags, (0, aql_1.aql) `${forStatementsFrag}`, ...rootAssignmentFrags, (0, aql_1.aql) `FOR ${itemVar} IN ${fieldTraversalFrag}`, generateSortAQL(node.orderBy, itemBaseContext), generateLimitClause(node) ?? (0, aql_1.aql) ``, ...itemAssignmentFrags, (0, aql_1.aql) `RETURN ${innerFrag}`);
|
|
1341
1501
|
}
|
|
1342
1502
|
/**
|
|
1343
1503
|
* Produces this if the result of the relation traversal is a single item:
|
|
@@ -1345,6 +1505,7 @@ function processTraversalWithRelationAndListFieldSegmentsUsingSubquery(node, con
|
|
|
1345
1505
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1346
1506
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1347
1507
|
* FOR root IN OUTBOUND hop2_target
|
|
1508
|
+
* LET extractedVar = NOEVAL(variableValueExpr(root))
|
|
1348
1509
|
* FOR item IN root.fieldSegment1[*].fieldSegment2[**][*
|
|
1349
1510
|
* FILTER filterExpr(CURRENT)
|
|
1350
1511
|
* LIMIT skip, maxCount
|
|
@@ -1371,6 +1532,7 @@ function processTraversalWithRelationAndListFieldSegmentsUsingSubquery(node, con
|
|
|
1371
1532
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1372
1533
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1373
1534
|
* LET root = FIRST(FOR node3 IN OUTBOUND hop2_target RETURN node3)
|
|
1535
|
+
* LET extractedVar = NOEVAL(variableValueExpr(root))
|
|
1374
1536
|
* FOR item IN root.fieldSegment1[*].fieldSegment2[**][*
|
|
1375
1537
|
* FILTER filterExpr(CURRENT)
|
|
1376
1538
|
* LIMIT skip, maxCount
|
|
@@ -1408,15 +1570,6 @@ function processTraversalWithRelationAndListFieldSegmentsUsingArrayExpansionWith
|
|
|
1408
1570
|
preserveNullValues: node.preserveNullValues,
|
|
1409
1571
|
context,
|
|
1410
1572
|
});
|
|
1411
|
-
const innerNode = node.innerNode;
|
|
1412
|
-
const innerMapFrag = innerNode
|
|
1413
|
-
? (itemFrag) => {
|
|
1414
|
-
const innerContext = context
|
|
1415
|
-
.bindVariable(node.itemVariable, itemFrag)
|
|
1416
|
-
.bindVariable(node.rootEntityVariable, rootVar);
|
|
1417
|
-
return processNode(innerNode, innerContext);
|
|
1418
|
-
}
|
|
1419
|
-
: undefined;
|
|
1420
1573
|
// We can do the LIMIT within the field traversal's mapping function using an array expansion,
|
|
1421
1574
|
// but only if the relation traversal yields at most one result (i.e. if it only follows 1:1 relations)
|
|
1422
1575
|
const lastRelationSegment = node.relationSegments[node.relationSegments.length - 1];
|
|
@@ -1436,8 +1589,10 @@ function processTraversalWithRelationAndListFieldSegmentsUsingArrayExpansionWith
|
|
|
1436
1589
|
maxCount: node.maxCount,
|
|
1437
1590
|
};
|
|
1438
1591
|
}
|
|
1439
|
-
|
|
1592
|
+
// There is no place to put item-dependent LET statements in this variant, so we don't pass an itemVar
|
|
1593
|
+
const { independentAssignmentFrags, rootAssignmentFrags, rootContext, processedInnerNode } = extractTraversalAssignmentsAsAql({ node, context, rootVar });
|
|
1440
1594
|
const filterNode = node.filterNode;
|
|
1595
|
+
let innerFilterFrag;
|
|
1441
1596
|
if (filterNode) {
|
|
1442
1597
|
innerFilterFrag = (itemFrag) => {
|
|
1443
1598
|
// don't map rootEntityVariable
|
|
@@ -1446,6 +1601,9 @@ function processTraversalWithRelationAndListFieldSegmentsUsingArrayExpansionWith
|
|
|
1446
1601
|
return processNode(filterNode, innerContext);
|
|
1447
1602
|
};
|
|
1448
1603
|
}
|
|
1604
|
+
const innerMapFrag = processedInnerNode
|
|
1605
|
+
? (itemFrag) => processNode(processedInnerNode, rootContext.bindVariable(node.itemVariable, itemFrag))
|
|
1606
|
+
: undefined;
|
|
1449
1607
|
const fieldTraversalFrag = getFieldTraversalFragment({
|
|
1450
1608
|
segments: node.fieldSegments,
|
|
1451
1609
|
sourceFrag: rootVar,
|
|
@@ -1465,8 +1623,8 @@ function processTraversalWithRelationAndListFieldSegmentsUsingArrayExpansionWith
|
|
|
1465
1623
|
// FOR item IN root.children
|
|
1466
1624
|
// RETURN item
|
|
1467
1625
|
// (could also use FLATTEN(), but since we're already using nested FORs, this is probably cleaner)
|
|
1468
|
-
const itemVar = aql_1.aql.variable(
|
|
1469
|
-
return aqlExt.subquery((0, aql_1.aql) `${forStatementsFrag}`, (0, aql_1.aql) `FOR ${itemVar} IN ${fieldTraversalFrag}`, generateLimitClause(limitArgs) ?? (0, aql_1.aql) ``, (0, aql_1.aql) `RETURN ${itemVar}`);
|
|
1626
|
+
const itemVar = aql_1.aql.variable('item');
|
|
1627
|
+
return aqlExt.subquery(...independentAssignmentFrags, (0, aql_1.aql) `${forStatementsFrag}`, ...rootAssignmentFrags, (0, aql_1.aql) `FOR ${itemVar} IN ${fieldTraversalFrag}`, generateLimitClause(limitArgs) ?? (0, aql_1.aql) ``, (0, aql_1.aql) `RETURN ${itemVar}`);
|
|
1470
1628
|
}
|
|
1471
1629
|
/**
|
|
1472
1630
|
* Produces:
|
|
@@ -1474,6 +1632,7 @@ function processTraversalWithRelationAndListFieldSegmentsUsingArrayExpansionWith
|
|
|
1474
1632
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1475
1633
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1476
1634
|
* FOR root IN OUTBOUND hop2_target
|
|
1635
|
+
* LET extractedVar = NOEVAL(variableValueExpr(root))
|
|
1477
1636
|
* FOR item IN root.fieldSegment1[*].fieldSegment2[**][*
|
|
1478
1637
|
* FILTER filterExpr(CURRENT)
|
|
1479
1638
|
* RETURN {
|
|
@@ -1493,6 +1652,7 @@ function processTraversalWithRelationAndListFieldSegmentsUsingArrayExpansionWith
|
|
|
1493
1652
|
* FOR node1 IN OUTBOUND source_hop1
|
|
1494
1653
|
* FOR node2 in OUTBOUND hop1_hop2
|
|
1495
1654
|
* LET root = FIRST(FOR node3 IN OUTBOUND hop2_target RETURN node3)
|
|
1655
|
+
* LET extractedVar = NOEVAL(variableValueExpr(root))
|
|
1496
1656
|
* FOR item IN root.fieldSegment1[*].fieldSegment2[**][*
|
|
1497
1657
|
* FILTER filterExpr(CURRENT)
|
|
1498
1658
|
* RETURN {
|
|
@@ -1558,14 +1718,17 @@ function processTraversalWithRelationAndListFieldSegmentsUsingArrayExpansionWith
|
|
|
1558
1718
|
if there are many / large fields in the items that are not needed at all
|
|
1559
1719
|
(because sorting probably copies the whole item into some temporary structure)
|
|
1560
1720
|
*/
|
|
1721
|
+
// There is no place to put item-dependent LET statements in this variant, so we don't pass an itemVar
|
|
1722
|
+
const { processedInnerNode, independentAssignmentFrags, rootAssignmentFrags, rootContext } = extractTraversalAssignmentsAsAql({ node, context, rootVar });
|
|
1561
1723
|
// we sort after mapping roots to items, so we need to preserve the sort values that are based on the item
|
|
1562
1724
|
// -> we produce items like this: { value: ..., sortValues: [...] }
|
|
1563
1725
|
const innerMapFrag = (itemFrag) => {
|
|
1564
|
-
const innerContext =
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1726
|
+
const innerContext = rootContext.bindVariable(node.itemVariable, itemFrag);
|
|
1727
|
+
const valueFrag = processedInnerNode
|
|
1728
|
+
? processNode(processedInnerNode, innerContext)
|
|
1729
|
+
: itemFrag;
|
|
1730
|
+
const sortValueFrags = node.orderBy.clauses.map((c) => processNode(c.valueNode, innerContext));
|
|
1731
|
+
return aql_1.aql.lines((0, aql_1.aql) `{`, aql_1.aql.indent(aql_1.aql.lines((0, aql_1.aql) `value: ${valueFrag},`, (0, aql_1.aql) `sortValues: [`, aql_1.aql.indent(aql_1.aql.join(sortValueFrags, (0, aql_1.aql) `,\n`)), (0, aql_1.aql) `]`)), (0, aql_1.aql) `}`);
|
|
1569
1732
|
};
|
|
1570
1733
|
const filterNode = node.filterNode;
|
|
1571
1734
|
const innerFilterFrag = filterNode
|
|
@@ -1587,7 +1750,7 @@ function processTraversalWithRelationAndListFieldSegmentsUsingArrayExpansionWith
|
|
|
1587
1750
|
const clauseFrags = node.orderBy.clauses.map((clause, index) => (0, aql_1.aql) `${itemVar}.sortValues[${aql_1.aql.integer(index)}]${dirAQL(clause.direction)}`);
|
|
1588
1751
|
// The fieldTraversalFrag will produce a list, so a simple RETURN mapFrag would result in nested lists
|
|
1589
1752
|
// -> we iterate over the items again to flatten the lists (the FOR ${itemVar} ...)
|
|
1590
|
-
return aqlExt.subquery((0, aql_1.aql) `${forStatementsFrag}`, (0, aql_1.aql) `FOR ${itemVar} IN ${fieldTraversalFrag}`, (0, aql_1.aql) `SORT ${aql_1.aql.join(clauseFrags, (0, aql_1.aql) `, `)}`, generateLimitClause(node) ?? (0, aql_1.aql) ``, (0, aql_1.aql) `RETURN ${itemVar}.value`);
|
|
1753
|
+
return aqlExt.subquery(...independentAssignmentFrags, (0, aql_1.aql) `${forStatementsFrag}`, ...rootAssignmentFrags, (0, aql_1.aql) `FOR ${itemVar} IN ${fieldTraversalFrag}`, (0, aql_1.aql) `SORT ${aql_1.aql.join(clauseFrags, (0, aql_1.aql) `, `)}`, generateLimitClause(node) ?? (0, aql_1.aql) ``, (0, aql_1.aql) `RETURN ${itemVar}.value`);
|
|
1591
1754
|
}
|
|
1592
1755
|
/**
|
|
1593
1756
|
* Produces:
|
|
@@ -1640,6 +1803,7 @@ function processTraversalWithOnlyFieldSegmentsUsingArrayExpansion(node, context)
|
|
|
1640
1803
|
* FILTER filterExpr(item)
|
|
1641
1804
|
* SORT item.sortField1 ASC, item.sortField1 DESC
|
|
1642
1805
|
* LIMIT skip, maxCount
|
|
1806
|
+
* LET extractedVar = variableValueExpr(item)
|
|
1643
1807
|
* RETURN innerNode(item)
|
|
1644
1808
|
*/
|
|
1645
1809
|
function processTraversalWithOnlyFieldSegmentsUsingSubquery(node, context) {
|
|
@@ -1668,15 +1832,14 @@ function processTraversalWithOnlyFieldSegmentsUsingSubquery(node, context) {
|
|
|
1668
1832
|
segments: node.fieldSegments,
|
|
1669
1833
|
sourceFrag,
|
|
1670
1834
|
});
|
|
1671
|
-
const itemVar = aql_1.aql.variable('item');
|
|
1672
|
-
const
|
|
1673
|
-
const returnValueFrag = node.innerNode ? processNode(node.innerNode, innerContext) : itemVar;
|
|
1835
|
+
const itemVar = aql_1.aql.variable(node.itemVariable.label ?? 'item');
|
|
1836
|
+
const { independentAssignmentFrags, itemAssignmentFrags, itemBaseContext, innerFrag } = extractTraversalAssignmentsAsAql({ node, context, itemVar });
|
|
1674
1837
|
// filter in the subquery instead of in getFieldTraversalFragment() because the filter
|
|
1675
1838
|
// expression might use subqueries
|
|
1676
1839
|
const filterFrag = node.filterNode
|
|
1677
|
-
? (0, aql_1.aql) `FILTER ${processNode(node.filterNode,
|
|
1840
|
+
? (0, aql_1.aql) `FILTER ${processNode(node.filterNode, itemBaseContext)}`
|
|
1678
1841
|
: (0, aql_1.aql) ``;
|
|
1679
|
-
return aqlExt.subquery((0, aql_1.aql) `FOR ${itemVar}`, (0, aql_1.aql) `IN ${fieldTraversalFrag}`, filterFrag, generateSortAQL(node.orderBy,
|
|
1842
|
+
return aqlExt.subquery(...independentAssignmentFrags, (0, aql_1.aql) `FOR ${itemVar}`, (0, aql_1.aql) `IN ${fieldTraversalFrag}`, filterFrag, generateSortAQL(node.orderBy, itemBaseContext), generateLimitClause(node) ?? (0, aql_1.aql) ``, ...itemAssignmentFrags, (0, aql_1.aql) `RETURN ${innerFrag}`);
|
|
1680
1843
|
}
|
|
1681
1844
|
function getRelationTraversalForStatements({ node, innermostItemVar, context, preserveNullValues = false, }) {
|
|
1682
1845
|
if (!node.relationSegments.length) {
|