UNPKG

marklogic

Version:

The official MarkLogic Node.js client API.

1,556 lines (1,471 loc) 184 kB
/* * Copyright © 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ 'use strict'; var util = require("util"); var deepcopy = require('deepcopy'); var mlutil = require('./mlutil.js'); const types = require('./server-types-generated.js'); const bldrbase = require('./plan-builder-base.js'); var comparisons = { '<' : 'LT', '<=' : 'LE', '>' : 'GT', '>=' : 'GE', '=' : 'EQ', '!=' : 'NE' }; var datatypes = { 'xs:anyURI': 'xs:anyURI', 'xs:date': 'xs:date', 'xs:dateTime': 'xs:dateTime', 'xs:dayTimeDuration': 'xs:dayTimeDuration', 'xs:decimal': 'xs:decimal', 'xs:double': 'xs:double', 'xs:float': 'xs:float', 'xs:gDay': 'xs:gDay', 'xs:gMonth': 'xs:gMonth', 'xs:gMonthDay': 'xs:gMonthDay', 'xs:gYear': 'xs:gYear', 'xs:gYearMonth': 'xs:gYearMonth', 'xs:int': 'xs:int', 'xs:long': 'xs:long', 'xs:string': 'xs:string', 'xs:time': 'xs:time', 'xs:unsignedInt': 'xs:unsignedInt', 'xs:unsignedLong': 'xs:unsignedLong', 'xs:yearMonthDuration': 'xs:yearMonthDuration' }; /** @ignore */ function asIndex(index) { return (typeof index === 'string' || index instanceof String) ? property(index) : index; } /** @ignore */ function addIndex(query, index, isContainer) { var containerOnly = (isContainer || false); if (index instanceof JSONPropertyDef) { query['json-property'] = index['json-property']; } else if (index instanceof ElementDef) { query.element = index.element; } else if (index instanceof AttributeDef) { query.element = index.element; query.attribute = index.attribute; } else if (containerOnly) { } else if (index instanceof FieldDef) { query.field = index.field; } else if (index instanceof PathIndexDef) { query['path-index'] = index['path-index']; } } /** * A helper for building the definition of a document query. The helper is * created by the {@link marklogic.queryBuilder} function. * @namespace queryBuilder */ /** * A composable query. * @typedef {object} queryBuilder.Query * @since 1.0 */ /** * An indexed name such as a JSON property, {@link queryBuilder#element} or * {@link queryBuilder#attribute}, {@link queryBuilder#field}, * or {@link queryBuilder#pathIndex}. * @typedef {object} queryBuilder.IndexedName * @since 1.0 */ /** * An indexed name such as a JSON property, XML element, or path that * represents a geospatial location for matched by a geospatial query. * @typedef {object} queryBuilder.GeoLocation * @since 1.0 */ /** * The specification of a point or an area (such as a box, circle, or polygon) * for use as criteria in a geospatial query. * @typedef {object} queryBuilder.Region * @since 1.0 */ /** * The specification of the latitude and longitude * returned by the {@link queryBuilder#latlon} function * for a coordinate of a {@link queryBuilder.Region}. * @typedef {object} queryBuilder.LatLon * @since 1.0 */ /** * The specification of the coordinate system * returned by the {@link queryBuilder#coordSystem} function * for a geospatial path index * @typedef {object} queryBuilder.CoordSystem * @since 1.0 */ /** @ignore */ function checkQueryArray(queryArray) { if (queryArray == null) { return queryArray; } var max = queryArray.length; if (max === 0) { return queryArray; } var i = 0; for (; i < max; i++) { checkQuery(queryArray[i]); } return queryArray; } /** @ignore */ function checkQuery(query) { if (typeof query === 'object' && query !== null) { if (!(query instanceof QueryDef)) { if (query instanceof ConstraintDef) { throw new Error('subquery must supply literal criteria for: '+query.name); } throw new Error('subquery is not a query definition: '+mlutil.identify(query, true)); } } return query; } /** @ignore */ function QueryBuilder() { if (!(this instanceof QueryBuilder)) { return new QueryBuilder(); } } /** @ignore */ function QueryDef() { if (!(this instanceof QueryDef)) { return new QueryDef(); } } /** @ignore */ function ConstraintDef() { if (!(this instanceof ConstraintDef)) { return new ConstraintDef(); } } /** * Builds a query for the intersection of the subqueries. * @method * @since 1.0 * @memberof queryBuilder# * @param {...queryBuilder.Query} subquery - a word, value, range, geospatial, * or other query or a composer such as an or query. * @param {queryBuilder.OrderParam} [ordering] - the ordering on the subqueries * returned from {@link queryBuilder#ordered} * @returns {queryBuilder.Query} a composable query */ function and() { var args = mlutil.asArray.apply(null, arguments); var queries = []; var ordered = null; var arg = null; for (var i=0; i < args.length; i++) { arg = args[i]; if (ordered === null) { if (arg instanceof OrderedDef) { ordered = arg.ordered; continue; } else if (typeof arg === 'boolean') { ordered = arg; continue; } } if (arg instanceof Array){ Array.prototype.push.apply(queries, arg); } else { queries.push(arg); } } return new AndDef(new QueryListDef(queries, ordered, null, null)); } /** @ignore */ function AndDef(queryList) { if (!(this instanceof AndDef)) { return new AndDef(queryList); } if (queryList != null) { if (queryList instanceof QueryListDef) { this['and-query'] = queryList; } else { throw new Error('invalid and() query: '+mlutil.identify(queryList, true)); } } else { this['and-query'] = new QueryListDef([]); } } util.inherits(AndDef, QueryDef); /** @ignore */ function QueryListDef(queries, ordered, weight, distance) { if (!(this instanceof QueryListDef)) { return new QueryListDef(queries, ordered, weight, distance); } if (Array.isArray(queries)) { this.queries = checkQueryArray(queries); } else { throw new Error('invalid query list: '+mlutil.identify(queries, true)); } if (ordered != null) { if (typeof ordered === 'boolean') { this.ordered = ordered; } else { throw new Error('invalid order for query list: '+mlutil.identify(ordered, true)); } } if (weight != null) { if (typeof weight === 'number' || weight instanceof Number) { this.weight = weight; } else { throw new Error('invalid weight for query list: '+mlutil.identify(weight, true)); } } if (distance != null) { if (typeof distance === 'number' || distance instanceof Number) { this.distance = distance; } else { throw new Error('invalid distance for query list: '+mlutil.identify(distance, true)); } } } /** * Builds a query with positive and negative subqueries. * @method * @since 1.0 * @memberof queryBuilder# * @param {queryBuilder.Query} positiveQuery - a query that must match * the result documents * @param {queryBuilder.Query} negativeQuery - a query that must not match * the result documents * @returns {queryBuilder.Query} a composable query */ function andNot() { var args = mlutil.asArray.apply(null, arguments); switch(args.length) { case 0: throw new Error('missing positive and negative queries'); case 1: throw new Error('has positive but missing negative query: '+mlutil.identify(args[0], true)); case 2: return new AndNotDef(new PositiveNegativeDef(args[0], args[1])); default: throw new Error('more than two arguments for andNot(): '+args.length); } } /** @ignore */ function AndNotDef(positiveNegative) { if (!(this instanceof AndNotDef)) { return new AndNotDef(positiveNegative); } if (positiveNegative instanceof PositiveNegativeDef) { this['and-not-query'] = positiveNegative; } else { throw new Error('invalid andNot() query: '+mlutil.identify(positiveNegative, true)); } } util.inherits(AndNotDef, QueryDef); /** @ignore */ function PositiveNegativeDef(positive, negative) { if (!(this instanceof PositiveNegativeDef)) { return new PositiveNegativeDef(positive, negative); } if (positive != null) { this['positive-query'] = checkQuery(positive); } else { throw new Error('missing positive query'); } if (negative != null) { this['negative-query'] = checkQuery(negative); } else { throw new Error('missing negative query'); } } /** * Specifies an XML attribute for a query. A name without a namespace can be * expressed as a string. A namespaced name can be expressed as a two-item * array with uri and name strings or as an object returned by the * {@link queryBuilder#qname} function. * @method * @since 1.0 * @memberof queryBuilder# * @param {string|string[]|queryBuilder.QName} element - the name of the element * @param {string|string[]|queryBuilder.QName} attribute - the name of the attribute * @returns {queryBuilder.IndexedName} an indexed name for specifying a query */ function attribute() { var args = mlutil.asArray.apply(null, arguments); switch(args.length) { case 0: throw new Error('missing element and attribute'); case 1: throw new Error('has element but missing attribute: '+mlutil.identify(args[0], true)); case 2: return new AttributeDef( (args[0] instanceof QNameDef) ? args[0] : Array.isArray(args[0]) ? qname.call(null, args[0]) : new QNameDef(null, args[0]), (args[1] instanceof QNameDef) ? args[1] : Array.isArray(args[1]) ? qname.call(null, args[1]) : new QNameDef(null, args[1]) ); case 3: if (args[0] instanceof QNameDef) { return new AttributeDef( args[0], new QNameDef(args[1], args[2]) ); } else if (args[2] instanceof QNameDef) { return new AttributeDef( new QNameDef(args[0], args[1]), args[2] ); } else if (Array.isArray(args[0])) { return new AttributeDef( qname.call(null, args[0]), new QNameDef(args[1], args[2]) ); } else if (Array.isArray(args[2])) { return new AttributeDef( new QNameDef(args[0], args[1]), qname.call(null, args[2]) ); } return new AttributeDef( new QNameDef(args[0], args[1]), new QNameDef(null, args[2]) ); default: return new AttributeDef( new QNameDef(args[0], args[1]), new QNameDef(args[2], args[3]) ); } } /** @ignore */ function AttributeDef(elemQName, attrQName) { if (!(this instanceof AttributeDef)) { return new AttributeDef(elemQName, attrQName); } if (elemQName instanceof QNameDef) { this.element = elemQName; } else { throw new Error('invalid element QName for attribute identifier: '+mlutil.identify(elemQName, true)); } if (attrQName instanceof QNameDef) { this.attribute = attrQName; } else { throw new Error('invalid attribute QName for identifier: '+mlutil.identify(attrQName, true)); } } /** * Builds a query with matching and boosting subqueries. * @method * @since 1.0 * @memberof queryBuilder# * @param {queryBuilder.Query} matchingQuery - a query that must match * the result documents * @param {queryBuilder.Query} boostingQuery - a query that increases * the ranking when qualifying result documents * @returns {queryBuilder.Query} a composable query */ function boost() { var args = mlutil.asArray.apply(null, arguments); switch(args.length) { case 0: throw new Error('missing matching and boosting queries'); case 1: throw new Error('has matching but missing boosting query: '+mlutil.identify(args[0], true)); case 2: return new BoostDef(new MatchingBoostingDef(args[0], args[1])); default: throw new Error('too many arguments for boost(): '+args.length); } } /** @ignore */ function BoostDef(matchingBoosting) { if (!(this instanceof BoostDef)) { return new BoostDef(matchingBoosting); } if (matchingBoosting instanceof MatchingBoostingDef) { this['boost-query'] = matchingBoosting; } else { throw new Error('invalid boost() query: '+mlutil.identify(matchingBoosting, true)); } } util.inherits(BoostDef, QueryDef); /** @ignore */ function MatchingBoostingDef(matching, boosting) { if (!(this instanceof MatchingBoostingDef)) { return new MatchingBoostingDef(matching, boosting); } if (matching != null) { this['matching-query'] = checkQuery(matching); } else { throw new Error('missing matching query'); } if (boosting != null) { this['boosting-query'] = checkQuery(boosting); } else { throw new Error('missing boosting query'); } } /** * Specifies a rectangular region with the coordinates of the corners. * The coordinates can be specified either by passing the return value * from the {@link queryBuilder#southWestNorthEast} function or as a list * of {queryBuilder.LatLon} coordinates in South, West, North, and East order. * @method * @since 1.0 * @memberof queryBuilder# * @param {queryBuilder.LatLon} south - the south coordinate of the box * @param {queryBuilder.LatLon} west - the west coordinate for the box * @param {queryBuilder.LatLon} north - the north coordinate for the box * @param {queryBuilder.LatLon} east - the east coordinate for the box * @returns {queryBuilder.Region} the region criteria for a geospatial query */ function box() { var args = mlutil.asArray.apply(null, arguments); switch(args.length) { case 0: throw new Error('missing four corners for box'); case 1: if (args[0] instanceof BoxDef) { return new BoxRegionDef(args[0]); } throw new Error('invalid box corners: '+mlutil.identify(args[0], true)); case 2: throw new Error('has two corners but missing two corners for box'); case 3: throw new Error('has three corners but missing one corner for box'); case 4: return new BoxRegionDef(new BoxDef(args[0], args[1], args[2], args[3])); default: throw new Error('too many arguments for box: '+args.length); } } /** @ignore */ function BoxRegionDef(box) { if (!(this instanceof BoxRegionDef)) { return new BoxRegionDef(box); } if (box instanceof BoxDef) { this.box = box; } else if (box == null) { throw new Error('missing definition for box region'); } else { throw new Error('invalid definition for box region: '+mlutil.identify(box, true)); } } /** * Specifies a circular region based on a radius and the coordinate of the center. * The coordinate can either be specified by passing the return value from * the {@link queryBuilder#latlon} function or by passing the latitude and longitude * numbers in that order (possibly wrapped in an array). * @method * @since 1.0 * @memberof queryBuilder# * @param {number} radius - the radius for the circle * @param {queryBuilder.LatLon} center - the center for the circle * @returns {queryBuilder.Region} the region criteria for a geospatial query */ function circle() { var args = mlutil.asArray.apply(null, arguments); var arg = null; switch(args.length) { case 0: throw new Error('missing radius and center for circle'); case 1: throw new Error('has radius but missing center for circle'); case 2: arg = args[1]; if (arg instanceof PointRegionDef) { if (arg.point.length === 1) { return new CircleRegionDef(new CircleDef(args[0], arg.point)); } } else if (arg instanceof LatLongDef) { return new CircleRegionDef(new CircleDef(args[0], [arg])); } else if (Array.isArray(arg) && arg.length === 2) { return new CircleRegionDef(new CircleDef( args[0], [new LatLongDef(arg[0], arg[1])] )); } throw new Error('invalid center for circle: '+mlutil.identify(arg, true)); case 3: return new CircleRegionDef(new CircleDef( args[0], [new LatLongDef(args[1], args[2])] )); default: throw new Error('too many arguments for circle: '+args.length); } } /** @ignore */ function CircleRegionDef(circle) { if (!(this instanceof CircleRegionDef)) { return new CircleRegionDef(circle); } if (circle instanceof CircleDef) { this.circle = circle; } else if (circle == null) { throw new Error('missing definition for circle region'); } else { throw new Error('invalid definition for circle region: '+mlutil.identify(circle, true)); } } /** @ignore */ function CircleDef(radius, center) { if (!(this instanceof CircleDef)) { return new CircleDef(radius, center); } if (typeof radius === 'number' || radius instanceof Number) { this.radius = radius; } else if (radius == null) { throw new Error('missing radius for circle'); } else { throw new Error('invalid radius for circle: '+mlutil.identify(radius, true)); } if (Array.isArray(center) && center.length === 1 && center[0] instanceof LatLongDef) { this.point = center; } else if (center == null) { throw new Error('missing center for circle'); } else { throw new Error('invalid center for circle: '+mlutil.identify(center, true)); } } /** * Builds a query matching documents in one or more collections as part * of a document query. The collections can be specified as arguments or * parsed from a query string based on a binding. Also, as part of a values * query, the collection() function identifies the collection index * without supplying criteria. In a values query, the tuples (aka rows) * projected from each document have a column whose values are the * collections to which the document belongs. * @method * @since 1.0 * @memberof queryBuilder# * @param {string|string[]|queryBuilder.BindingParam} collections - either * one or more collection uris to match or exactly one binding (returned * by the {@link queryBuilder#bind} function) for parsing the collection * uris from a query string; required except for values queries * @param {string} [prefix] - a prefix to prepend to each value provided by * the parsed query string; can be provided only when providing a binding * @returns {queryBuilder.Query} a composable query */ function collection() { var args = mlutil.asArray.apply(null, arguments); var argLen = args.length; if (argLen === 0) { return new CollectionConstraintDef(null, null, null); } var constraintName = null; var suggestOptions = null; var prefix = null; var arg = null; var i = 0; for (; i < argLen; i++) { arg = args[i]; if (constraintName === null && arg instanceof BindDef) { constraintName = arg.constraintName; continue; } if (suggestOptions === null && arg instanceof SuggestOptionsDef) { suggestOptions = arg['suggest-option']; continue; } if (argLen < 4 && prefix === null && (typeof arg === 'string' || arg instanceof String)) { prefix = arg; continue; } break; } if (constraintName != null) { if (i < argLen) { throw new Error('collection has both query and binding: '+mlutil.identify(args, true)); } return new CollectionConstraintDef( constraintName, suggestOptions, new CollectionQualifierDef(prefix) ); } if (suggestOptions != null) { throw new Error('collection has both query and suggest options: '+mlutil.identify(args, true)); } return new CollectionQueryDef( new UrisDef(args, null) ); } /** @ignore */ function CollectionConstraintDef(constraintName, suggestOptions, qualifierDef) { if (!(this instanceof CollectionConstraintDef)) { return new CollectionConstraintDef(qualifierDef); } if (constraintName === null && suggestOptions === null && qualifierDef === null) { this.collection = null; } else { if (qualifierDef instanceof CollectionQualifierDef) { this.collection = qualifierDef; } else if (qualifierDef == null) { throw new Error('missing qualifier definition for collection binding'); } else { throw new Error('invalid qualifier definition for collection binding: '+ mlutil.identify(qualifierDef, true)); } addConstraint(this, constraintName, suggestOptions); } } util.inherits(CollectionConstraintDef, ConstraintDef); /** @ignore */ function CollectionQualifierDef(prefix) { if (!(this instanceof CollectionQualifierDef)) { return new CollectionQualifierDef(prefix); } if (typeof prefix === 'string' || prefix instanceof String) { this.prefix = prefix; } else if (prefix != null) { throw new Error('invalid prefix for collection binding: '+mlutil.identify(prefix, true)); } this.facet = false; } /** @ignore */ function CollectionQueryDef(uriList) { if (!(this instanceof CollectionQueryDef)) { return new CollectionQueryDef(uriList); } if (uriList instanceof UrisDef) { this['collection-query'] = uriList; } else { throw new Error('invalid uri list for collection(): '+mlutil.identify(uriList, true)); } } util.inherits(CollectionQueryDef, QueryDef); /** * Builds a query matching temporal documents with a system start time * prior to the LSQT (Latest System Query Time). Advancing the LSQT can be * done manually or on an automated basis to include more recent * temporal documents in the result set. * @method * @since 1.0 * @memberof queryBuilder# * @param {string} temporalCollection - the name of the temporal collection * that retains the temporal documents * @param {queryBuilder.WeightParam} [weight] - a weight returned * by {@link queryBuilder#weight} to increase or decrease the score * of subqueries relative to other queries in the complete search * @param {string|Date} [timestamp] - a datetime older than the LSQT * to use as the upper boundary for an older view of the database * @param {queryBuilder.TemporalOptionsParam} [temporalOptions] - a list * of options returned by {@link queryBuilder#temporalOptions} to modify * the temporal query * @returns {queryBuilder.Query} a composable query */ function lsqtQuery() { var args = mlutil.asArray.apply(null, arguments); var argLen = args.length; if (argLen < 1) { throw new Error('no temporal collection for lsqt query'); } var temporalCollection = null; var weight = null; var timestamp = null; var temporalOptions = null; var arg = null; for (var i=0; i < args.length; i++) { arg = args[i]; if (i === 0) { if (typeof arg !== 'string' && !(arg instanceof String)) { throw new Error('first argument for lsqt query must be temporal collection: '+ mlutil.identify(arg, true)); } temporalCollection = arg; continue; } if (weight === null && arg instanceof WeightDef) { weight = arg.weight; continue; } if (timestamp === null) { if (typeof arg === 'string' || arg instanceof String){ timestamp = arg; continue; } else if (Object.prototype.toString.call(arg) === '[object Date]') { timestamp = arg.toISOString(); continue; } } if (temporalOptions === null && arg instanceof TemporalOptionsDef) { temporalOptions = arg['temporal-option']; continue; } throw new Error('unknown argument for lsqt query: '+mlutil.identify(arg, true)); } return new LSQTDef( new LSQTQueryDef(temporalCollection, weight, timestamp, temporalOptions) ); } /** @ignore */ function LSQTDef(queryDef) { if (!(this instanceof LSQTDef)) { return new LSQTDef(queryDef); } if (queryDef instanceof LSQTQueryDef) { this['lsqt-query'] = queryDef; } else if (queryDef == null) { throw new Error('missing LSQT query definition'); } else { throw new Error('invalid LSQT query definition: '+mlutil.identify(queryDef, true)); } } util.inherits(LSQTDef, QueryDef); /** @ignore */ function LSQTQueryDef(temporalCollection, weight, timestamp, temporalOptions) { if (!(this instanceof LSQTQueryDef)) { return new LSQTQueryDef(temporalCollection, weight, timestamp, temporalOptions); } if (typeof temporalCollection === 'string' || temporalCollection instanceof String) { this['temporal-collection'] = temporalCollection; } else if (temporalCollection == null) { throw new Error('missing temporal collection'); } else { throw new Error('invalid temporal collection: '+mlutil.identify(temporalCollection, true)); } if (weight != null) { if (typeof weight === 'number' || weight instanceof Number) { this.weight = weight; } else { throw new Error('invalid weight: '+mlutil.identify(weight, true)); } } if (timestamp != null) { if (typeof timestamp === 'string' || timestamp instanceof String) { this.timestamp = timestamp; } else { throw new Error('invalid timestamp: '+mlutil.identify(timestamp, true)); } } if (temporalOptions != null) { this['temporal-option'] = temporalOptions; } } /** * Builds a query naming a JSON property or XML element that must contain * the matches for a subquery (which may be a composer query such as those * returned by the {@link queryBuilder#and} and {@link queryBuilder#or}). * @method * @since 1.0 * @memberof queryBuilder# * @param {string|queryBuilder.IndexedName} propertyOrElement - the JSON * property or XML element that contains the query matches; a string is * treated as a JSON property * @param {queryBuilder.Query|queryBuilder.BindingParam} query - either the * query that must match within the scope of the JSON property or XML element * or a binding (returned by the {@link queryBuilder#bind} function) for * parsing the subquery from a query string * @param {queryBuilder.FragmentScopeParam} [fragmentScope] - whether the query * applies to document content (the default) or document metadata properties * as returned by the {@link queryBuilder#fragmentScope} function * @returns {queryBuilder.Query} a composable query */ function scope() { var args = mlutil.asArray.apply(null, arguments); if (args.length < 1) { throw new Error('element or property scope not specified'); } var index = null; var constraintName = null; var fragmentScope = null; var query = null; var arg = null; for (var i=0; i < args.length; i++) { arg = args[i]; if (i === 0) { index = asIndex(arg); continue; } if (fragmentScope === null && arg instanceof FragmentScopeDef) { fragmentScope = arg['fragment-scope']; continue; } if (constraintName === null && arg instanceof BindDef) { constraintName = arg.constraintName; continue; } // TODO: validate arg is a query if (query === null) { query = arg; continue; } throw new Error('unknown argument for scope: '+mlutil.identify(arg, true)); } if (query != null) { if (constraintName != null) { throw new Error('scope has both binding and query: '+mlutil.identify(args, true)); } return new ContainerQueryDef( new ContainedDef(index, fragmentScope, query) ); } if (constraintName == null) { constraintName = defaultConstraintName(index); if (constraintName == null) { throw new Error('could not default constraint name from '+ Object.keys(index).join(', ') + ' index' ); } } return new ContainerConstraintDef( constraintName, new ContainedDef(index, fragmentScope, null) ); } /** @ignore */ function ContainerConstraintDef(constraintName, containedDef) { if (!(this instanceof ContainerConstraintDef)) { return new ContainerConstraintDef(containedDef); } if (typeof constraintName === 'string' || constraintName instanceof String) { this.name = constraintName; } else if (constraintName == null) { throw new Error('missing constraint name for container'); } else { throw new Error('invalid constraint name for container: '+mlutil.identify(constraintName, true)); } if (containedDef instanceof ContainedDef) { this.container = containedDef; } else if (containedDef == null) { throw new Error('missing contained definition for container binding'); } else { throw new Error('invalid contained definition for container binding: '+mlutil.identify(containedDef, true)); } } util.inherits(ContainerConstraintDef, ConstraintDef); /** @ignore */ function ContainerQueryDef(containedDef) { if (!(this instanceof ContainerQueryDef)) { return new ContainerQueryDef(containedDef); } if (containedDef instanceof ContainedDef) { this['container-query'] = containedDef; } else if (containedDef == null) { throw new Error('missing contained definition for container query'); } else { throw new Error('invalid contained definition for container query: '+mlutil.identify(containedDef, true)); } } util.inherits(ContainerQueryDef, QueryDef); /** @ignore */ function ContainedDef(index, fragmentScope, query) { if (!(this instanceof ContainedDef)) { return new ContainedDef(index, fragmentScope, query); } if (index != null) { addIndex(this, index, true); } else { throw new Error('missing JSON property or XML element'); } if (fragmentScope != null) { this['fragment-scope'] = fragmentScope; } if (query != null) { var queryKeys = Object.keys(query); var queryKey = null; for (var i=0; i < queryKeys.length; i++) { queryKey = queryKeys[i]; this[queryKey] = query[queryKey]; } } } /** * The datatype specification returned by the {@link queryBuilder#datatype} * function. * @typedef {object} queryBuilder.DatatypeParam * @since 1.0 */ /** * Identifies the datatype of an index. * @method * @since 1.0 * @memberof queryBuilder# * @param {string} datatype - a value from the enumeration * int|unsignedInt|long|unsignedLong|float|double|decimal|dateTime|time|date|gYearMonth|gYear|gMonth|gDay|yearMonthDuration|dayTimeDuration|string|anyURI|point * @param {string} [collation] - a URI identifying the comparison method for a string or anyURI datatype * @returns {queryBuilder.DatatypeParam} a datatype specification */ function datatype() { var args = mlutil.asArray.apply(null, arguments); switch(args.length) { case 0: throw new Error('missing datatype'); case 1: return new DatatypeDef(args[0]); case 2: return new DatatypeDef(args[0], args[1]); default: throw new Error('too many arguments for datatype: '+args.length); } } /** @ignore */ function DatatypeDef(datatype, collation) { if (!(this instanceof DatatypeDef)) { return new DatatypeDef(datatype, collation); } if (typeof datatype === 'string' || datatype instanceof String) { this.datatype = (datatype.indexOf(':') !== -1) ? datatype : 'xs:'+datatype; } else if (datatype == null) { throw new Error('missing datatype'); } else { throw new Error('invalid datatype: '+mlutil.identify(datatype, true)); } if (typeof collation === 'string' || collation instanceof String) { this.collation = collation; } else if (collation != null) { throw new Error('invalid collation for datatype: '+mlutil.identify(collation, true)); } } /** * Builds a query matching documents in one or more database directories. * @method * @since 1.0 * @memberof queryBuilder# * @param {string|string[]} uris - one or more directory uris * to match * @param {boolean} [infinite] - whether to match documents at the top level or * at any level of depth within the specified directories * @returns {queryBuilder.Query} a composable query */ function directory() { var args = mlutil.asArray.apply(null, arguments); var uris = []; var infinite = null; var arg = null; for (var i=0; i < args.length; i++) { arg = args[i]; if (infinite === null && (typeof arg === 'boolean')) { infinite = arg; } else if (arg instanceof Array){ Array.prototype.push.apply(uris, arg); } else { uris.push(arg); } } return new DirectoryQueryDef( new UrisDef(uris, infinite) ); } /** @ignore */ function DirectoryQueryDef(uriList) { if (!(this instanceof DirectoryQueryDef)) { return new DirectoryQueryDef(uriList); } if (uriList instanceof UrisDef) { this['directory-query'] = uriList; } else { throw new Error('invalid uri list for directory(): '+mlutil.identify(uriList, true)); } } util.inherits(DirectoryQueryDef, QueryDef); /** * Builds a query matching documents. * @method * @since 1.0 * @memberof queryBuilder# * @param {string|string[]} uris - one or more document uris * to match * @returns {queryBuilder.Query} a composable query */ function document() { return new DocumentDef( new UrisDef(mlutil.asArray.apply(null, arguments), null) ); } /** @ignore */ function DocumentDef(uriList) { if (!(this instanceof DocumentDef)) { return new DocumentDef(uriList); } if (uriList instanceof UrisDef) { this['document-query'] = uriList; } else { throw new Error('invalid uri list for document(): '+mlutil.identify(uriList, true)); } } util.inherits(DocumentDef, QueryDef); /** @ignore */ function UrisDef(uris, infinite) { if (!(this instanceof UrisDef)) { return new UrisDef(uris, infinite); } if (!Array.isArray(uris)) { throw new Error('invalid uri list: '+mlutil.identify(uris, true)); } else if (uris.length === 0) { throw new Error('empty uri list'); } else { this.uri = uris; } if (infinite != null) { if (typeof infinite !== 'boolean') { throw new Error('infinite flag must be boolean: '+mlutil.identify(infinite, true)); } else { this.infinite = infinite; } } } /** * Builds a query that applies the subquery to document content by contrast * with the {@link queryBuilder#properties} function. * @method * @since 1.0 * @memberof queryBuilder# * @param {queryBuilder.Query} query - the query that must match document content * @returns {queryBuilder.Query} a composable query */ function documentFragment() { return new DocumentFragmentDef(mlutil.first.apply(null, arguments)); } /** @ignore */ function DocumentFragmentDef(query) { if (!(this instanceof DocumentFragmentDef)) { return new DocumentFragmentDef(query); } if (query != null) { this['document-fragment-query'] = checkQuery(query); } else { throw new Error('missing query'); } } util.inherits(DocumentFragmentDef, QueryDef); /** * Specifies an XML element for a query. A name without a namespace can be * expressed as a string. A namespaced name can be expressed as a two-item * array with uri and name strings or as an object returned by the * {@link queryBuilder#qname} function. * @method * @since 1.0 * @memberof queryBuilder# * @param {string|string[]|queryBuilder.QName} name - the name of the element * @returns {queryBuilder.IndexedName} an indexed name for specifying a query */ function element() { var args = mlutil.asArray.apply(null, arguments); switch(args.length) { case 0: throw new Error('missing element name'); case 1: if (args[0] instanceof QNameDef) { return new ElementDef(args[0]); } return new ElementDef(new QNameDef(null, args[0])); case 2: return new ElementDef(new QNameDef(args[0], args[1])); default: throw new Error('too many arguments for element identifier: '+args.length); } } /** @ignore */ function ElementDef(qname) { if (!(this instanceof ElementDef)) { return new ElementDef(qname); } if (qname instanceof QNameDef) { this.element = qname; } else if (qname == null) { throw new Error('missing QName for element identifier'); } else { throw new Error('invalid QName for element identifier: '+mlutil.identify(qname, true)); } } /** * Specifies a field for a query. * @method * @since 1.0 * @memberof queryBuilder# * @param {string} name - the name of the field * @param {string} [collation] - the collation of a field over strings * @returns {queryBuilder.IndexedName} an indexed name for specifying a query */ function field() { var args = mlutil.asArray.apply(null, arguments); switch(args.length) { case 0: throw new Error('missing field name'); case 1: return new FieldDef(new FieldNameDef(args[0])); case 2: return new FieldDef(new FieldNameDef(args[0], args[1])); default: throw new Error('too many arguments for field identifier: '+args.length); } } /** @ignore */ function FieldDef(name) { if (!(this instanceof FieldDef)) { return new FieldDef(name); } if (name instanceof FieldNameDef) { this.field = name; } else if (name == null) { throw new Error('missing name for field identifier'); } else { throw new Error('invalid name for field identifier: '+mlutil.identify(name, true)); } } /** @ignore */ function FieldNameDef(name, collation) { if (!(this instanceof FieldNameDef)) { return new FieldNameDef(name, collation); } if (typeof name === 'string' || name instanceof String) { this.name = name; } else if (name == null) { throw new Error('missing name for field'); } else { throw new Error('invalid name for field: '+mlutil.identify(name, true)); } if (typeof collation === 'string' || collation instanceof String) { this.collation = collation; } else if (collation != null) { throw new Error('invalid collation for field: '+mlutil.identify(collation, true)); } } /** * A query argument specifying whether queries match documents based on * document content or document metadata properties; returned * by the {@link queryBuilder#fragmentScope} function. * @typedef {object} queryBuilder.FragmentScopeParam * @since 1.0 */ /** * Configures a query to match documents based on document content or * document metadata properties. * @method * @since 1.0 * @memberof queryBuilder# * @param {string} scopeType - a value from the documents|properties * enumeration where 'documents' queries document content and * 'properties' queries document metadata properties * @returns {queryBuilder.FragmentScopeParam} a fragment scope specification */ function fragmentScope() { return new FragmentScopeDef(mlutil.first.apply(null, arguments)); } /** @ignore */ function FragmentScopeDef(scope) { if (!(this instanceof FragmentScopeDef)) { return new FragmentScopeDef(scope); } if (scope === 'documents' || scope === 'properties') { this['fragment-scope'] = scope; } else if (scope == null) { throw new Error('missing scope for fragment scope'); } else { throw new Error('invalid scope for fragment scope: '+mlutil.identify(scope, true)); } } /** * Specifies the geospatial locations represented by an XML attribute pair * for passing to the {@link queryBuilder#geospatial} function. * @method * @since 1.0 * @memberof queryBuilder# * @param {string|queryBuilder.QName} parent - the name of the element * containing the attributes as returned by the {@link queryBuilder#qname} function * @param {string|queryBuilder.QName} latitude - the name of the latitude * attribute as returned by the {@link queryBuilder#qname} function * @param {string|queryBuilder.QName} longitude - the name of the longitude * attribute as returned by the {@link queryBuilder#qname} function * @returns {queryBuilder.GeoLocation} the specification for the geospatial locations */ function geoAttributePair() { var args = mlutil.asArray.apply(null, arguments); if (args.length < 2) { throw new Error('need at least two parameters for geospatial attribute pair query'); } var location = {}; var keys = ['parent', 'lat', 'lon']; var iArg=0; for (var i=0; i < keys.length; i++) { var key = keys[i]; var arg = args[iArg++]; if (arg instanceof QNameDef) { location[key] = arg; } else if (typeof arg === 'string' || arg instanceof String) { location[key] = new QNameDef(null, arg); } else if (arg instanceof ElementDef) { location.parent = arg.element; } else if (arg instanceof AttributeDef) { if (key === 'parent' || !location.parent) { location.parent = arg.element; i++; } location[keys[i]] = arg.attribute; } else { throw new Error('no parameter for '+key+': '+JSON.stringify(arg)); } } return {'geo-attr-pair': location}; } /** * Specifies the geospatial locations represented by an XML element * containing a comma-separated pair of latitude-longitude values * for passing to the {@link queryBuilder#geospatial} function. * @method * @since 1.0 * @memberof queryBuilder# * @param {string|queryBuilder.QName} [parent] - the optional name of the parent * of the geospatial element as returned by the {@link queryBuilder#qname} function * @param {string|queryBuilder.QName} element - the name of the element * as returned by the {@link queryBuilder#qname} function * @returns {queryBuilder.GeoLocation} the specification for the geospatial locations */ function geoElement() { var args = mlutil.asArray.apply(null, arguments); var argLen = args.length; if (argLen < 1) { throw new Error('need at least one parameter for geospatial element query'); } var location = {}; // TODO: elementName var maxIndex = Math.min(argLen, 2); var elemName = null; var arg = null; var i=0; while (i < maxIndex) { arg = args[i]; if (arg instanceof QNameDef) { } else if (typeof arg === 'string' || arg instanceof String) { arg = new QNameDef(null, arg); } else if (arg instanceof ElementDef) { arg = arg.element; } else { break; } if (elemName !== null) { location.parent = elemName; } elemName = arg; i++; } if (elemName === null) { throw new Error('element name required for geospatial location: '+mlutil.identify(args, true)); } location.element = elemName; elemName = null; return {'geo-elem': location}; } /** * Specifies the geospatial locations represented by an XML element pair * for passing to the {@link queryBuilder#geospatial} function. * @method * @since 1.0 * @memberof queryBuilder# * @param {string|queryBuilder.QName} parent - the name of the containing * parent element as returned by the {@link queryBuilder#qname} function * @param {string|queryBuilder.QName} latitude - the name of the latitude * element as returned by the {@link queryBuilder#qname} function * @param {string|queryBuilder.QName} longitude - the name of the longitude * element as returned by the {@link queryBuilder#qname} function * @returns {queryBuilder.GeoLocation} the specification for the geospatial locations */ function geoElementPair() { var args = mlutil.asArray.apply(null, arguments); if (args.length < 2) { throw new Error('need at least two parameters for geospatial element pair query'); } var location = {}; var keys = ['parent', 'lat', 'lon']; var key = null; var arg = null; for (var i=0; i < keys.length; i++) { key = keys[i]; arg = args[i]; if (arg instanceof QNameDef) { location[key] = arg; } else if (typeof arg === 'string' || arg instanceof String) { location[key] = new QNameDef(null, arg); } else if (arg instanceof ElementDef) { location[key] = arg.element; } else { throw new Error('no parameter for '+key+': '+JSON.stringify(arg)); } } return {'geo-elem-pair': location}; } /** * Specifies the geospatial locations represented by a JSON property * containing a pair of latitude-longitude values for passing to * the {@link queryBuilder#geospatial} function. * @method * @since 1.0 * @memberof queryBuilder# * @param {string} [parent] - the optional name of the parent * of the geospatial property * @param {string} element - the name of the geospatial property * @returns {queryBuilder.GeoLocation} the specification for the geospatial locations */ function geoProperty() { var args = mlutil.asArray.apply(null, arguments); var argLen = args.length; if (argLen < 1) { throw new Error('need at least one parameters for geospatial property query'); } var location = {}; var maxIndex = Math.min(argLen, 2); var propName = null; var arg = null; var i=0; while (i < maxIndex) { arg = args[i]; if (arg instanceof JSONPropertyDef) { arg = arg['json-property']; } else if (typeof arg !== 'string' && !(arg instanceof String)) { break; } if (propName !== null) { location['parent-property'] = propName; } propName = arg; i++; } if (propName === null) { throw new Error('property name required for geospatial location: '+mlutil.identify(args, true)); } location['json-property'] = propName; propName = null; return {'geo-json-property': location}; } /** * Specifies the geospatial locations represented by a JSON property pair * for passing to the {@link queryBuilder#geospatial} function. * @method * @since 1.0 * @memberof queryBuilder# * @param {string} parent - the name of the containing parent property * @param {string} latitude - the name of the latitude property * @param {string} longitude - the name of the longitude property * @returns {queryBuilder.GeoLocation} the specification for the geospatial locations */ function geoPropertyPair() { var args = mlutil.asArray.apply(null, arguments); if (args.length < 2) { throw new Error('need at least two parameters for geospatial property pair query'); } var location = {}; var keys = ['parent-property', 'lat-property', 'lon-property']; var key = null; var arg = null; for (var i=0; i < keys.length; i++) { key = keys[i]; arg = args[i]; if (typeof arg === 'string' || arg instanceof String) { location[key] = arg; } else if (arg instanceof JSONPropertyDef) { location[key] = arg['json-property']; } else { throw new Error('no parameter for '+key+': '+JSON.stringify(arg)); } } return {'geo-json-property-pair': location}; } /** * Specifies the geospatial locations represented by a path index on * JSON properties or XML elements containing a pair of latitude-longitude values * for passing to the {@link queryBuilder#geospatial} function. * @method * @since 1.0 * @memberof queryBuilder# * @param {string|object} path - the XPath for the path index as a string or * as the return value of the {@link queryBuilder#pathIndex} function * @param {object} namespaces - bindings between the prefixes in the path and * namespace URIs * @param {queryBuilder.CoordSystem} coordSystem - the coordinate system for the index * @returns {queryBuilder.GeoLocation} the specification for the geospatial locations */ function geoPath() { var args = mlutil.asArray.apply(null, arguments); if (args.length < 1) { throw new Error('need at least one parameter for geospatial path query'); } var location = {}; var seekingPathIndex = true; var firstArg = null; var arg = null; var i=0; for (; i < args.length; i++) { arg = args[i]; if (i === 0) { if (arg instanceof PathIndexDef) { location['path-index'] = arg['path-index']; seekingPathIndex = false; } else if (arg['path-index'] != null) { location['path-index'] = arg['path-index']; firstArg = arg.coordSystem; if (firstArg != null) { location.coordSystem = firstArg; } seekingPathIndex = false; break; } else if (typeof arg === 'string' || arg instanceof String) { firstArg = arg; } else { throw new Error('geospatial path without index location'); } continue; } if (arg instanceof CoordSystemDef) { location.coord = arg.coord; continue; } if (seekingPathIndex) { seekingPathIndex = false; if (typeof arg === 'object' && arg !== null) { location['path-index'] = new PathDef(firstArg , arg); continue; } else { location['path-index'] = new PathDef(firstArg); } } throw new Error('geospatial path with unknown argument: '+arg); } if (seekingPathIndex) { location['path-index'] = new PathDef(firstArg); } return {'geo-path': location}; } /** * Builds a geospatial query or facet against a geospatial point index. * For a query, you must supply either the {queryBuilder.Region} criteria * or a binding to parse the region criteria from a query string but not * both. * @method * @since 1.0 * @memberof queryBuilder# * @param {queryBuilder.GeoLocation} location - the JSON property or XML element * representing the geospatial locations * @param {queryBuilder.WeightParam} [weight] - a weight returned * by {@link queryBuilder#weight} to increase or decrease the score * of the query relative to other queries in the complete search * @param {queryBuilder.FragmentScopeParam} [fragmentScope] - whether the query * applies to document content (the default) or document metadata properties * as returned by the {@link queryBuilder#fragmentScope} function * @param {queryBuilder.GeoOptionsParam} [geoOptions] - a list * of options returned by {@link queryBuilder#geoOptions} to modify * the geospatial query * @param {queryBuilder.Region|queryBuilder.BindingParam} [criteria] - either * a point matching or region containing geospatial locations in the documents * or a binding (returned by the {@link queryBuilder#bind} function) for parsing * the point or region from a query string * @returns {queryBuilder.Query} a composable query */ function geospatial() { /*jshint validthis:true */ return geospatialImpl.call( this, false, mlutil.asArray.apply(null, arguments) ); } /** * Builds a geospatial query or facet against a geospatial region index. * For a query, you must supply either the {queryBuilder.Region} criteria * or a binding to parse the region criteria from a query string but not * both. * @method * @since 2.0.1 * @memberof queryBuilder# * @param {queryBuilder.GeoLocation} location - the JSON property or XML element * repr