marklogic
Version:
The official MarkLogic Node.js client API.
1,556 lines (1,471 loc) • 184 kB
JavaScript
/*
* 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