UNPKG

marklogic

Version:

The official MarkLogic Node.js client API.

488 lines (451 loc) 18.4 kB
/* * 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 } };