-
Notifications
You must be signed in to change notification settings - Fork 53
Expand file tree
/
Copy pathvalues-builder.js
More file actions
487 lines (451 loc) · 18 KB
/
Copy pathvalues-builder.js
File metadata and controls
487 lines (451 loc) · 18 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
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
/*
* Copyright (c) 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/
'use strict';
const mlutil = require('./mlutil.js');
const qb = require('./query-builder.js').builder;
/**
* A source of datatyped values from a JSON property,
* XML element or attribute, field, or path range or
* geospatial index or from the collection or uri index.
* @typedef {object} valuesBuilder.DatatypedValuesIndex
* @since 1.0
*/
/**
* An object representing the result of building a query using
* the helper functions of a {@link valuesBuilder}. You must call
* the {@link valuesBuilder#fromIndexes} function to create the initial
* BuiltQuery for the values query before calling the functions
* to specify the other clauses of the built query.
* @namespace valuesBuilder.BuiltQuery
*/
/**
* A helper for building the definition of a values query, which projects
* tuples (aka rows) of values out of documents. The helper is created by
* the {@link marklogic.valuesBuilder} function. You must call
* the {@link valuesBuilder#fromIndexes} function to supply the required
* clause of the values query before calling the
* {@link valuesBuilder.BuiltQuery#where},
* {@link valuesBuilder.BuiltQuery#aggregates},
* {@link valuesBuilder.BuiltQuery#slice}, or
* {@link valuesBuilder.BuiltQuery#withOptions}
* functions to specify the optional clauses of the built query.
* @namespace valuesBuilder
* @borrows queryBuilder#and as valuesBuilder#and
* @borrows queryBuilder#andNot as valuesBuilder#andNot
* @borrows queryBuilder#attribute as valuesBuilder#attribute
* @borrows queryBuilder#boost as valuesBuilder#boost
* @borrows queryBuilder#box as valuesBuilder#box
* @borrows queryBuilder#circle as valuesBuilder#circle
* @borrows queryBuilder#collection as valuesBuilder#collection
* @borrows queryBuilder#datatype as valuesBuilder#datatype
* @borrows queryBuilder#directory as valuesBuilder#directory
* @borrows queryBuilder#document as valuesBuilder#document
* @borrows queryBuilder#documentFragment as valuesBuilder#documentFragment
* @borrows queryBuilder#element as valuesBuilder#element
* @borrows queryBuilder#falseQuery as valuesBuilder#falseQuery
* @borrows queryBuilder#field as valuesBuilder#field
* @borrows queryBuilder#fragmentScope as valuesBuilder#fragmentScope
* @borrows queryBuilder#geoAttributePair as valuesBuilder#geoAttributePair
* @borrows queryBuilder#geoElement as valuesBuilder#geoElement
* @borrows queryBuilder#geoElementPair as valuesBuilder#geoElementPair
* @borrows queryBuilder#geoPath as valuesBuilder#geoPath
* @borrows queryBuilder#geoProperty as valuesBuilder#geoProperty
* @borrows queryBuilder#geoPropertyPair as valuesBuilder#geoPropertyPair
* @borrows queryBuilder#geoOptions as valuesBuilder#geoOptions
* @borrows queryBuilder#geospatial as valuesBuilder#geospatial
* @borrows queryBuilder#heatmap as valuesBuilder#heatmap
* @borrows queryBuilder#latlon as valuesBuilder#latlon
* @borrows queryBuilder#locksFragment as valuesBuilder#locksFragment
* @borrows queryBuilder#lsqtQuery as valuesBuilder#lsqtQuery
* @borrows queryBuilder#near as valuesBuilder#near
* @borrows queryBuilder#not as valuesBuilder#not
* @borrows queryBuilder#notIn as valuesBuilder#notIn
* @borrows queryBuilder#pathIndex as valuesBuilder#pathIndex
* @borrows queryBuilder#period as valuesBuilder#period
* @borrows queryBuilder#periodCompare as valuesBuilder#periodCompare
* @borrows queryBuilder#periodRange as valuesBuilder#periodRange
* @borrows queryBuilder#point as valuesBuilder#point
* @borrows queryBuilder#polygon as valuesBuilder#polygon
* @borrows queryBuilder#properties as valuesBuilder#properties
* @borrows queryBuilder#property as valuesBuilder#property
* @borrows queryBuilder#qname as valuesBuilder#qname
* @borrows queryBuilder#or as valuesBuilder#or
* @borrows queryBuilder#ordered as valuesBuilder#ordered
* @borrows queryBuilder#range as valuesBuilder#range
* @borrows queryBuilder#rangeOptions as valuesBuilder#rangeOptions
* @borrows queryBuilder#scope as valuesBuilder#scope
* @borrows queryBuilder#southWestNorthEast as valuesBuilder#southWestNorthEast
* @borrows queryBuilder#temporalOptions as valuesBuilder#temporalOptions
* @borrows queryBuilder#term as valuesBuilder#term
* @borrows queryBuilder#termOptions as valuesBuilder#termOptions
* @borrows queryBuilder#transform as valuesBuilder#transform
* @borrows queryBuilder#trueQuery as valuesBuilder#trueQuery
* @borrows queryBuilder#value as valuesBuilder#value
* @borrows queryBuilder#weight as valuesBuilder#weight
* @borrows queryBuilder#word as valuesBuilder#word
*/
function ValueBuilder() {
if (!(this instanceof ValueBuilder)) {
return new ValueBuilder();
}
}
/**
* Specifies the range or geospatial indexes or collection or uri lexicons
* from which to project columns in the response for the values query.
* The response has a tuple (aka row) for each co-occurrence of these indexes
* in the documents selected by the where clause.
* This function must be called on the values builder.
* @method valuesBuilder#fromIndexes
* @since 1.0
* @param {string|valuesBuilder.DatatypedValuesIndex} indexes - a list
* of parameters or array specifying the JSON properties, XML elements or attributes,
* fields, or paths providing the range or geospatial indexes or the return value
* from the collection() or uri() helper functions to specify those lexicons.
* @returns {valuesBuilder.BuiltQuery} a built query
*/
function valuesFromIndexes() {
/*jshint validthis:true */
const self = (this instanceof ValueBuilder) ? this : new ValueBuilder();
const args = mlutil.asArray.apply(null, arguments);
const argLen = args.length;
const isRangeIndex = {
'element': true,
'field': true,
'json-property': true,
'path-index': true
};
const isGeoLocationIndex = {
'geo-attr-pair': true,
'geo-elem': true,
'geo-elem-pair': true,
'geo-json-property': true,
'geo-json-property-pair': true,
'geo-path': true
};
let arg = null;
let firstKey = null;
let range = null;
for (let i=0; i < argLen; i++) {
arg = args[i];
if (typeof arg === 'string' || arg instanceof String) {
args[i] = {range: qb.property(arg)};
continue;
}
firstKey = Object.keys(arg)[0];
if (isRangeIndex[firstKey]) {
args[i] = {range: arg};
continue;
}
range = arg.range;
if (range != null) {
args[i] = {range: range};
}
if (isGeoLocationIndex[firstKey]) {
args[i] = arg;
continue;
}
}
self.fromIndexesClause = args;
return self;
}
ValueBuilder.prototype.fromIndexes = valuesFromIndexes;
/**
* Sets the where clause of a values query, using the helper functions
* of a {@link valuesBuilder} to specify a structured query.
* This function must be called on the builtQuery returned
* by the {@link valuesBuilder#valuesFromIndexes} function
* or another function specifying a values query clause.
* @method valuesBuilder.BuiltQuery#where
* @since 1.0
* @param {queryBuilder.Query} query - one or more composable queries
* returned by the values query builder functions.
* @returns {valuesBuilder.BuiltQuery} a built query
*/
ValueBuilder.prototype.where = function valuesWhere() {
const self = this;
const args = mlutil.asArray.apply(null, arguments);
const argLen = args.length;
// TODO: if empty, clear the clause
let parsedQuery = null;
let fragmentScope = null;
let queries = null;
switch(argLen) {
case 0:
// TODO: better to skip the clause
self.whereClause = {query: {queries: [qb.and()]}};
break;
default:
for (let i=0; i < argLen; i++) {
const arg = args[i];
if (parsedQuery == null) {
parsedQuery = arg.parsedQuery;
if (parsedQuery != null) {
continue;
}
}
if (fragmentScope == null) {
fragmentScope = arg['fragment-scope'];
if (fragmentScope != null) {
continue;
}
}
if (queries === null) {
queries = [arg];
} else {
queries.push(arg);
}
}
var whereClause = {};
if (queries !== null) {
whereClause.query = {queries: queries};
}
if (parsedQuery != null) {
whereClause.parsedQuery = parsedQuery;
}
if (fragmentScope != null) {
whereClause['fragment-scope'] = fragmentScope;
}
self.whereClause = whereClause;
break;
}
return self;
};
/**
* Specifies aggregates to calculate over the tuples (aka rows) projected
* from the indexes for the documents selected by the where clause.
* This function must be called on the builtQuery returned
* by the {@link valuesBuilder#valuesFromIndexes} function
* or another function specifying a values query clause.
* @method valuesBuilder.BuiltQuery#aggregates
* @since 1.0
* @param {string[]} functions - one or more built-in functions such
* avg, correlation, count, covariance, covariance-population, max, median, min,
* stddev, stddev-population, sum, variance, or variance-population or the
* return values from the {@link valuesBuilder#udf} function specifying
* a user-defined aggregate function.
* @returns {valuesBuilder.BuiltQuery} a built query
*/
ValueBuilder.prototype.aggregates = function valuesAggregates() {
const self = this;
const args = mlutil.asArray.apply(null, arguments);
const argLen = args.length;
if (argLen < 1) {
throw new Error('aggregates must specify at least one built-in function or UDF');
}
const aggregateFunctions = [];
for (let i=0; i < argLen; i++) {
const arg = args[i];
if (typeof arg === 'string' || arg instanceof String) {
aggregateFunctions.push({apply:arg});
} else if (arg.udf !== void 0) {
aggregateFunctions.push(arg);
} else if (Array.isArray(arg) && arg.length > 1) {
aggregateFunctions.push({udf:arg[0], apply:arg[1]});
}
}
self.aggregatesClause = {aggregates: aggregateFunctions};
return self;
};
/**
* Identifies a UDF (User Defined Function) that calculates
* a custom aggregate different from a range index.
* The plugin implementing the aggregate must have been
* installed in the database server distribution on all hosts
* where the database has forests.
* @method valuesBuilder#udf
* @since 1.0
* @param {string} pluginName - the name of the plugin library
* that implements the UDF
* @param {string} functionName - the name of the function
* that is the entry point within the library
* @returns {object} the definition of the UDF for passing
* to the {@link valuesBuilder#aggregates} function
*/
function udf() {
if (arguments.length < 1) {
throw new Error('UDF calls must specify the plugin and function');
}
return ({udf:arguments[0], apply:arguments[1]});
}
/**
* Sets the slice clause of a built query to select the slice
* of the result set based on the start tuple within the result set
* and the number of tuples (aka rows) in the slice.
* This function must be called on the builtQuery returned
* by the {@link valuesBuilder#valuesFromIndexes} function
* or another function specifying a values query clause.
* By default, the slice uses array slice mode, but you can switch
* to legacy slice mode with {@link marklogic.setSliceMode}. Legacy
* slice mode is deprecated and will be removed in the next major release.
* @method valuesBuilder.BuiltQuery#slice
* @since 1.0
* @param {number} start - in array slice mode, the zero-based position
* within the result set of the first tuple; in legacy slice mode, the
* one-based position within the result set of the first tuple or 0 to
* suppress the tuples and return only the summary
* @param {number} length - in array slice mode, the zero-based position
* of the tuple after the last document in the slice or 0 to suppress
* the documents and return only the summary; in legacy slice mode, the
* number of tuples in the slice
* @param {transform} [transform] - a transform to apply to the
* slice on the server as specified by the {@link valuesBuilder#transform}
* function
* @returns {valuesBuilder.BuiltQuery} a built query
*/
ValueBuilder.prototype.slice = function valuesSlice() {
const self = this;
const args = mlutil.asArray.apply(null, arguments);
// TODO: if empty, clear the clause
self.sliceClause = mlutil.makeSliceClause('values', args);
return self;
};
/**
* Sets the withOptions clause of a built query to configure the query;
* takes a configuration object with the following named parameters.
* This function must be called on the builtQuery returned
* by the {@link valuesBuilder#valuesFromIndexes} function
* or another function specifying a values query clause.
* @method valuesBuilder.BuiltQuery#withOptions
* @since 1.0
* @param {...string} [forestNames] - the names of forests providing documents
* for the result set
* @param {...string} [values] - options modifying the default behaviour of the query
* @returns {valuesBuilder.BuiltQuery} a built query
*/
ValueBuilder.prototype.withOptions = function valuesWithOptions() {
const self = this;
// TODO: share with values.js
const optionKeyMapping = {
values: 'values-option',
forestNames: 'forest-names'
};
// TODO: reuse key copy logic with query-build withOptions
const withOptionsClause = {};
if (0 < arguments.length) {
const arg = arguments[0];
const argKeys = Object.keys(arg);
for (let i=0; i < argKeys.length; i++) {
const key = argKeys[i];
if (optionKeyMapping[key] !== undefined) {
const value = arg[key];
if (value !== undefined) {
withOptionsClause[key] = value;
}
}
}
}
self.withOptionsClause = withOptionsClause;
return self;
};
/**
* Identifies the uri index as the source for a column of values.
* Each tuple (aka row) projected from a document includes the
* uri of the document as one of the columns.
* @method valuesBuilder#uri
* @since 1.0
* @returns {valuesBuilder.DatatypedValuesIndex} an identifier for the uri index
*/
function valuesUri() {
return {uri: null};
}
/**
* Initializes a new values builder by copying the from indexes clause
* and any where, aggregates, slice, or withOptions clause defined in
* the built query.
* @method valuesBuilder#copyFrom
* @since 1.0
* @param {valuesBuilder.BuiltQuery} query - an existing values query
* with clauses to copy
* @returns {valuesBuilder.BuiltQuery} a built query
*/
function copyFromValueBuilder(otherValueBuilder) {
const tb = new ValueBuilder();
// TODO: share with QueryBuilder
if (otherValueBuilder != null) {
const clauseKeys = [
'fromIndexesClause', 'whereClause', 'aggregatesClause',
'sliceClause', 'withOptionsClause'
];
const isString = (typeof otherValueBuilder === 'string' || otherValueBuilder instanceof String);
const other = isString ?
JSON.parse(otherValueBuilder) : otherValueBuilder;
for (let i=0; i < clauseKeys.length; i++){
const key = clauseKeys[i];
const value = other[key];
if (value != null) {
// structuredClone instead of clone to avoid preserving prototype
tb[key] = isString ? value : structuredClone(value);
}
}
}
return tb;
}
module.exports = {
builder: {
and: qb.and,
andNot: qb.andNot,
attribute: qb.attribute,
bind: qb.bind,
bindDefault: qb.bindDefault,
bindEmptyAs: qb.bindEmptyAs,
boost: qb.boost,
box: qb.box,
circle: qb.circle,
collection: qb.collection,
copyFrom: copyFromValueBuilder,
datatype: qb.datatype,
directory: qb.directory,
document: qb.document,
documentFragment: qb.documentFragment,
element: qb.element,
falseQuery: qb.falseQuery,
field: qb.field,
fragmentScope: qb.fragmentScope,
fromIndexes: valuesFromIndexes,
geoAttributePair: qb.geoAttributePair,
geoElement: qb.geoElement,
geoElementPair: qb.geoElementPair,
geoPath: qb.geoPath,
geoProperty: qb.geoProperty,
geoPropertyPair: qb.geoPropertyPair,
geoOptions: qb.geoOptions,
geospatial: qb.geospatial,
heatmap: qb.heatmap,
jsontype: qb.jsontype,
latlon: qb.latlon,
locksFragment: qb.locksFragment,
lsqtQuery: qb.lsqtQuery,
near: qb.near,
not: qb.not,
notIn: qb.notIn,
parseBindings: qb.parseBindings,
parsedFrom: qb.parsedFrom,
parseFunction: qb.parseFunction,
pathIndex: qb.pathIndex,
period: qb.period,
periodCompare: qb.periodCompare,
periodRange: qb.periodRange,
point: qb.point,
polygon: qb.polygon,
propertiesFragment: qb.propertiesFragment,
property: qb.property,
qname: qb.qname,
or: qb.or,
ordered: qb.ordered,
range: qb.range,
rangeOptions: qb.rangeOptions,
scope: qb.scope,
southWestNorthEast: qb.southWestNorthEast,
temporalOptions: qb.temporalOptions,
term: qb.term,
termOptions: qb.termOptions,
transform: qb.transform,
trueQuery: qb.trueQuery,
udf: udf,
uri: valuesUri,
value: qb.value,
weight: qb.weight,
word: qb.word
}
};