forked from canada-ca/tracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaql.js
More file actions
330 lines (330 loc) · 10.2 KB
/
Copy pathaql.js
File metadata and controls
330 lines (330 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.aql = exports.isAqlLiteral = exports.isGeneratedAqlQuery = exports.isAqlQuery = void 0;
/**
* ```js
* import { aql } from "arangojs/aql";
* ```
*
* The "aql" module provides the {@link aql} template string handler and
* helper functions, as well as associated types and interfaces for TypeScript.
*
* The aql function and namespace is also re-exported by the "index" module.
*
* @packageDocumentation
*/
const collection_1 = require("./collection");
const view_1 = require("./view");
/**
* Indicates whether the given value is an {@link AqlQuery}.
*
* @param query - A value that might be an `AqlQuery`.
*/
function isAqlQuery(query) {
return Boolean(query && typeof query.query === "string" && query.bindVars);
}
exports.isAqlQuery = isAqlQuery;
/**
* Indicates whether the given value is a {@link GeneratedAqlQuery}.
*
* @param query - A value that might be a `GeneratedAqlQuery`.
*
* @internal
* @hidden
*/
function isGeneratedAqlQuery(query) {
return isAqlQuery(query) && typeof query._source === "function";
}
exports.isGeneratedAqlQuery = isGeneratedAqlQuery;
/**
* Indicates whether the given value is an {@link AqlLiteral}.
*
* @param literal - A value that might be an `AqlLiteral`.
*/
function isAqlLiteral(literal) {
return Boolean(literal && typeof literal.toAQL === "function");
}
exports.isAqlLiteral = isAqlLiteral;
/**
* Template string handler (template tag) for AQL queries.
*
* The `aql` tag can be used to write complex AQL queries as multi-line strings
* without having to worry about `bindVars` and the distinction between
* collections and regular parameters.
*
* Tagged template strings will return an {@link AqlQuery} object with
* `query` and `bindVars` attributes reflecting any interpolated values.
*
* Any {@link ArangoCollection} instance used in a query string will be
* recognized as a collection reference and generate an AQL collection bind
* parameter instead of a regular AQL value bind parameter.
*
* **Note**: you should always use the `aql` template tag when writing
* dynamic AQL queries instead of using untagged (normal) template strings.
* Untagged template strings will inline any interpolated values and return
* a plain string as result. The `aql` template tag will only inline references
* to the interpolated values and produce an AQL query object containing both
* the query and the values. This prevents most injection attacks when using
* untrusted values in dynamic queries.
*
* @example
* ```js
* // Some user-supplied string that may be malicious
* const untrustedValue = req.body.email;
*
* // Without aql tag: BAD! DO NOT DO THIS!
* const badQuery = `
* FOR user IN users
* FILTER user.email == "${untrustedValue}"
* RETURN user
* `;
* // e.g. if untrustedValue is '" || user.admin == true || "':
* // Query:
* // FOR user IN users
* // FILTER user.email == "" || user.admin == true || ""
* // RETURN user
*
* // With the aql tag: GOOD! MUCH SAFER!
* const betterQuery = aql`
* FOR user IN users
* FILTER user.email == ${untrustedValue}
* RETURN user
* `;
* // Query:
* // FOR user IN users
* // FILTER user.email == @value0
* // RETURN user
* // Bind parameters:
* // value0 -> untrustedValue
* ```
*
* @example
* ```js
* const collection = db.collection("some-collection");
* const minValue = 23;
* const result = await db.query(aql`
* FOR d IN ${collection}
* FILTER d.num > ${minValue}
* RETURN d
* `);
*
* // Equivalent raw query object
* const result2 = await db.query({
* query: `
* FOR d IN @@collection
* FILTER d.num > @minValue
* RETURN d
* `,
* bindVars: {
* "@collection": collection.name,
* minValue: minValue
* }
* });
* ```
*
* @example
* ```js
* const collection = db.collection("some-collection");
* const color = "green";
* const filter = aql`FILTER d.color == ${color}'`;
* const result = await db.query(aql`
* FOR d IN ${collection}
* ${filter}
* RETURN d
* `);
* ```
*/
function aql(templateStrings, ...args) {
const strings = [...templateStrings];
const bindVars = {};
const bindValues = [];
let query = strings[0];
for (let i = 0; i < args.length; i++) {
const rawValue = args[i];
let value = rawValue;
if (isGeneratedAqlQuery(rawValue)) {
const src = rawValue._source();
if (src.args.length) {
query += src.strings[0];
args.splice(i, 1, ...src.args);
strings.splice(i, 2, strings[i] + src.strings[0], ...src.strings.slice(1, src.args.length), src.strings[src.args.length] + strings[i + 1]);
}
else {
query += rawValue.query + strings[i + 1];
args.splice(i, 1);
strings.splice(i, 2, strings[i] + rawValue.query + strings[i + 1]);
}
i -= 1;
continue;
}
if (rawValue === undefined) {
query += strings[i + 1];
continue;
}
if (isAqlLiteral(rawValue)) {
query += `${rawValue.toAQL()}${strings[i + 1]}`;
continue;
}
const index = bindValues.indexOf(rawValue);
const isKnown = index !== -1;
let name = `value${isKnown ? index : bindValues.length}`;
if (collection_1.isArangoCollection(rawValue) || view_1.isArangoView(rawValue)) {
name = `@${name}`;
value = rawValue.name;
}
if (!isKnown) {
bindValues.push(rawValue);
bindVars[name] = value;
}
query += `@${name}${strings[i + 1]}`;
}
return {
query,
bindVars,
_source: () => ({ strings, args }),
};
}
exports.aql = aql;
(function (aql) {
/**
* Marks an arbitrary scalar value (i.e. a string, number or boolean) as
* safe for being inlined directly into AQL queries when used in an `aql`
* template string, rather than being converted into a bind parameter.
*
* **Note**: Nesting `aql` template strings is a much safer alternative for
* most use cases. This low-level helper function only exists to help with
* rare edge cases where a trusted AQL query fragment must be read from a
* string (e.g. when reading query fragments from JSON) and should only be
* used as a last resort.
*
* @example
* ```js
* // BAD! DO NOT DO THIS!
* const sortDirection = aql.literal('ASC');
*
* // GOOD! DO THIS INSTEAD!
* const sortDirection = aql`ASC`;
* ```
*
* @example
* ```js
* // BAD! DO NOT DO THIS!
* const filterColor = aql.literal('FILTER d.color == "green"');
* const result = await db.query(aql`
* FOR d IN some-collection
* ${filterColor}
* RETURN d
* `);
*
* // GOOD! DO THIS INSTEAD!
* const color = "green";
* const filterColor = aql`FILTER d.color === ${color}`;
* const result = await db.query(aql`
* FOR d IN some-collection
* ${filterColor}
* RETURN d
* `);
* ```
*
* @example
* ```js
* // WARNING: We explicitly trust the environment variable to be safe!
* const filter = aql.literal(process.env.FILTER_STATEMENT);
* const users = await db.query(aql`
* FOR user IN users
* ${filter}
* RETURN user
* `);
* ```
*/
function literal(value) {
if (isAqlLiteral(value)) {
return value;
}
return {
toAQL() {
if (value === undefined) {
return "";
}
return String(value);
},
};
}
aql.literal = literal;
/**
* Constructs {@link AqlQuery} objects from an array of arbitrary values.
*
* **Note**: Nesting `aql` template strings is a much safer alternative
* for most use cases. This low-level helper function only exists to
* complement the `aql` tag when constructing complex queries from dynamic
* arrays of query fragments.
*
* @param values - Array of values to join. These values will behave exactly
* like values interpolated in an `aql` template string.
* @param sep - Seperator to insert between values. This value will behave
* exactly like a value passed to {@link aql.literal}, i.e. it will be
* inlined as-is, rather than being converted into a bind parameter.
*
* @example
* ```js
* const users = db.collection("users");
* const filters = [];
* if (adminsOnly) filters.push(aql`FILTER user.admin`);
* if (activeOnly) filters.push(aql`FILTER user.active`);
* const result = await db.query(aql`
* FOR user IN ${users}
* ${aql.join(filters)}
* RETURN user
* `);
* ```
*
* @example
* ```js
* const users = db.collection("users");
* const keys = ["jreyes", "ghermann"];
*
* // BAD! NEEDLESSLY COMPLEX!
* const docs = keys.map(key => aql`DOCUMENT(${users}, ${key}`));
* const result = await db.query(aql`
* FOR user IN [
* ${aql.join(docs, ", ")}
* ]
* RETURN user
* `);
* // Query:
* // FOR user IN [
* // DOCUMENT(@@value0, @value1), DOCUMENT(@@value0, @value2)
* // ]
* // RETURN user
* // Bind parameters:
* // @value0 -> "users"
* // value1 -> "jreyes"
* // value2 -> "ghermann"
*
* // GOOD! MUCH SIMPLER!
* const result = await db.query(aql`
* FOR key IN ${keys}
* LET user = DOCUMENT(${users}, key)
* RETURN user
* `);
* // Query:
* // FOR user IN @value0
* // LET user = DOCUMENT(@@value1, key)
* // RETURN user
* // Bind parameters:
* // value0 -> ["jreyes", "ghermann"]
* // @value1 -> "users"
* ```
*/
function join(values, sep = " ") {
if (!values.length) {
return aql ``;
}
if (values.length === 1) {
return aql `${values[0]}`;
}
return aql(["", ...Array(values.length - 1).fill(sep), ""], ...values);
}
aql.join = join;
})(aql = exports.aql || (exports.aql = {}));
//# sourceMappingURL=aql.js.map