diff --git a/lib/plan-builder-generated.js b/lib/plan-builder-generated.js index 16955d63..ebe596de 100755 --- a/lib/plan-builder-generated.js +++ b/lib/plan-builder-generated.js @@ -14,6 +14,7 @@ version numbers for "since" annotations. const types = require('./server-types-generated.js'); const bldrbase = require('./plan-builder-base.js'); let patchArgs = []; +let columnBuilderArgs = []; class CtsExpr { constructor() { @@ -5532,11 +5533,11 @@ trunc(...args) { * Returns a vector value. Provides a client interface to a server function. See {@link http://docs.marklogic.com/vec.vector|vec.vector} * @method planBuilder.vec#vector * @since 3.5.0 - * @param { XsAnyAtomicType } [values] - The value(s) to create the vector from. Can be a sequence or json:array of integer or floating-point numbers. Also accepts a string that has the format of a JSON array of Numbers. Also accepts a string that was created by vec:base64-encode(). + * @param { XsAnyAtomicType } [values] - The value(s) to create the vector from. Can be a sequence or json:array of integer or floating-point numbers. Also accepts a string that has the format of a JSON array of Numbers, a string that was created by vec:base64-encode(), or a types.Node such as returned by xpath. * @returns { VecVector } */ vector(...args) { - const paramdef = ['values', [types.XsAnyAtomicType, PlanColumn, PlanParam], false, true]; + const paramdef = ['values', [types.XsAnyAtomicType, types.Node, PlanColumn, PlanParam], false, true]; const checkedArgs = bldrbase.makeSingleArgs('vec.vector', 1, paramdef, args); return new types.VecVector('vec', 'vector', checkedArgs); } @@ -7135,12 +7136,167 @@ class PlanAnnTopKOptions extends types.ServerType { } } +/** + * ColumnBuilder object to construct a view for op.fromDocs. + * This class has been modified from the auto-generated classes to + * add chaining behavior to the methods to collect multiple method + * calls into a single ColumnBuilder object to send to /v1/rows. + * This is done by returning a new PlanColumnBuilderBase and follows + * the PatchBuilder pattern. + * @namespace planBuilder.ColumnBuilder + * @since 4.1.0 + */ +class PlanColumnBuilder extends types.ServerType { + + constructor(ns, fn, args) { + super(ns, fn, args); + } +/** + * Create a new column definition for use with op:from-docs. This method initializes the column with default properties unless overridden by additional chained methods. Default Behavior: If no other properties are set (such as xpath(), type(), or nullable()), the column defaults to: XPath: ./name (where name is the name passed to add-column()).Type: stringNullable: true Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.addColumn|op.addColumn} + * @method planBuilder.ColumnBuilder#addColumn + * @since 4.1.0 + * @param { PlanColumnName } [column] - The Column Builder Plan. You can either use the XQuery => chaining operator or specify the variable that captures the return value from the previous operation. + * @returns { planBuilder.ColumnBuilder } + */ +addColumn(...args) { + const paramdef = ['column', [PlanColumn, types.XsString], true, false]; + const checkedArgs = bldrbase.makeSingleArgs('PlanColumnBuilder.addColumn', 1, paramdef, args); + return new PlanColumnBuilderBase([new PlanColumnBuilder('op', 'add-column', checkedArgs)]); + } +/** + * Sets the collation for the column created by op:add-column. This only applies to columns with string types, as collations specify the order in which strings are sorted and how they are compared. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.collation|op.collation} + * @method planBuilder.ColumnBuilder#collation + * @since 4.1.0 + * @param { XsString } [collation] - the collation for string types + * @returns { planBuilder.ColumnBuilder } + */ +collation(...args) { + const paramdef = ['collation', [types.XsString], true, false]; + const checkedArgs = bldrbase.makeSingleArgs('PlanColumnBuilder.collation', 1, paramdef, args); + return new PlanColumnBuilderBase([new PlanColumnBuilder('op', 'collation', checkedArgs)]); + } +/** + * Sets the coordinate-system for the column created by op:add-column. This only applies to columns with geo types. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.coordinateSystem|op.coordinateSystem} + * @method planBuilder.ColumnBuilder#coordinateSystem + * @since 4.1.0 + * @param { XsString } [coordinateSystem] - the coordinate system for geo types + * @returns { planBuilder.ColumnBuilder } + */ +coordinateSystem(...args) { + const paramdef = ['coordinate-system', [types.XsString], true, false]; + const checkedArgs = bldrbase.makeSingleArgs('PlanColumnBuilder.coordinateSystem', 1, paramdef, args); + return new PlanColumnBuilderBase([new PlanColumnBuilder('op', 'coordinate-system', checkedArgs)]); + } +/** + * Sets the default value for the column created by op:add-column. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.default|op.default} + * @method planBuilder.ColumnBuilder#default + * @since 4.1.0 + * @param { Item } [defaultExpression] - the default value for the column + * @returns { planBuilder.ColumnBuilder } + */ +default(...args) { + const paramdef = ['default-expression', [types.Item, PlanColumn, PlanParam], true, false]; + const checkedArgs = bldrbase.makeSingleArgs('PlanColumnBuilder.default', 1, paramdef, args); + return new PlanColumnBuilderBase([new PlanColumnBuilder('op', 'default', checkedArgs)]); + } +/** + * Sets the vector dimension for the column created by op:add-column. It only applies when the column's type is set to "vector". If the vector to be extracted does not have the same dimension, the value will be rejected. There's no default dimension. Vectors of all dimensions are extracted. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.dimension|op.dimension} + * @method planBuilder.ColumnBuilder#dimension + * @since 4.1.0 + * @param { XsUnsignedInt } [dimension] - the vector dimension for the column + * @returns { planBuilder.ColumnBuilder } + */ +dimension(...args) { + const paramdef = ['dimension', [types.XsUnsignedInt], true, false]; + const checkedArgs = bldrbase.makeSingleArgs('PlanColumnBuilder.dimension', 1, paramdef, args); + return new PlanColumnBuilderBase([new PlanColumnBuilder('op', 'dimension', checkedArgs)]); + } +/** + * Sets the expression to create content or extract content from a document for the column created by op:add-column. It cannot be used together with op:xpath, as both of them define the content of the column. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.expr|op.expr} + * @method planBuilder.ColumnBuilder#expr + * @since 4.1.0 + * @param { Item } [expression] - the expression to create content or extract content from a document for the column + * @returns { planBuilder.ColumnBuilder } + */ +expr(...args) { + const paramdef = ['expression', [types.Item, PlanColumn, PlanParam], true, false]; + const checkedArgs = bldrbase.makeSingleArgs('PlanColumnBuilder.expr', 1, paramdef, args); + return new PlanColumnBuilderBase([new PlanColumnBuilder('op', 'expr', checkedArgs)]); + } +/** + * Set the column created by op:add-column nullable or not. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.nullable|op.nullable} + * @method planBuilder.ColumnBuilder#nullable + * @since 4.1.0 + * @param { XsBoolean } [bool] - whether the column is nullable + * @returns { planBuilder.ColumnBuilder } + */ +nullable(...args) { + const paramdef = ['bool', [types.XsBoolean], true, false]; + const checkedArgs = bldrbase.makeSingleArgs('PlanColumnBuilder.nullable', 1, paramdef, args); + return new PlanColumnBuilderBase([new PlanColumnBuilder('op', 'nullable', checkedArgs)]); + } +/** + * Set the data type of the column created by op:add-column. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.type|op.type} + * @method planBuilder.ColumnBuilder#type + * @since 4.1.0 + * @param { XsString } [type] - the data type of the column + * @returns { planBuilder.ColumnBuilder } + */ +type(...args) { + const paramdef = ['type', [types.XsString], true, false]; + const checkedArgs = bldrbase.makeSingleArgs('PlanColumnBuilder.type', 1, paramdef, args); + return new PlanColumnBuilderBase([new PlanColumnBuilder('op', 'type', checkedArgs)]); + } +/** + * Sets the units for the column created by op:add-column. This only applies to columns with geo types. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.units|op.units} + * @method planBuilder.ColumnBuilder#units + * @since 4.1.0 + * @param { XsString } [units] - the units for the column + * @returns { planBuilder.ColumnBuilder } + */ +units(...args) { + const paramdef = ['units', [types.XsString], true, false]; + const checkedArgs = bldrbase.makeSingleArgs('PlanColumnBuilder.units', 1, paramdef, args); + return new PlanColumnBuilderBase([new PlanColumnBuilder('op', 'units', checkedArgs)]); + } +/** + * This function extracts a sequence of child nodes from a column with node values -- especially, the document nodes from a document join. The path is an XPath (specified as a string) to apply to each node to generate a sequence of nodes as an expression value. Sets the XPath expression in the document from which to extract content for the column created by op:add-column. It cannot be used together with op:expr, as both of them define the content of the column. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.xpath|op.xpath} + * @method planBuilder.ColumnBuilder#xpath + * @since 4.1.0 + * @param { XsString } [path] - The name of the column from which to extract the child nodes. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function.It can also be op:context to refer to the node of the current processing row. + * @returns { planBuilder.ColumnBuilder } + */ +xpath(...args) { + const paramdef = ['path', [types.XsString], true, false]; + const checkedArgs = bldrbase.makeSingleArgs('PlanColumnBuilder.xpath', 1, paramdef, args); + return new PlanColumnBuilderBase([new PlanColumnBuilder('op', 'xpath', checkedArgs)]); + } +} + +/** + * Helper class to collect column builder operations. + * @namespace planBuilder.PlanColumnBuilderBase + * @since 4.1.0 + */ +class PlanColumnBuilderBase { + constructor(args) { + columnBuilderArgs.push(args); + return new PlanColumnBuilder('op', 'suboperators', columnBuilderArgs); + } +} class PlanJsonProperty extends types.ServerType { constructor(ns, fn, args) { super(ns, fn, args); } +} +class PlanContextExprCall extends types.ServerType { + + constructor(ns, fn, args) { + super(ns, fn, args); + } + } class PlanCtsReferenceMap extends types.ServerType { @@ -8104,24 +8260,6 @@ shortestPath(...args) { bldrbase.makePositionalArgs('PlanModifyPlan.shortestPath', 2, false, paramdefs, args); return new PlanModifyPlan(this, 'op', 'shortest-path', checkedArgs); - } -/** - * This method performs a transitive closure operation over a graph-like structure, identifying all reachable node pairs from a given start node to an end node through one or more intermediate steps. A set of (start, end) node pairs where a path exists between them with a length between minLength and maxLength, inclusive. This models the SPARQL one-or-more (+) operator, enabling recursive or chained relationships to be queried efficiently. Provides a client interface to a server function. See {@link http://docs.marklogic.com/ModifyPlan.prototype.transitiveClosure|ModifyPlan.prototype.transitiveClosure} - * @method planBuilder.ModifyPlan#transitiveClosure - * @since 4.1.0 - * @param { PlanExprColName } [start] - The column is the starting node of the traversal. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. - * @param { PlanExprColName } [end] - The column is the end node of the traversal. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. - * @param { PlanTransitiveClosureOptions } [options] - This is either a sequence of strings or an object containing keys and values for the options to this operator. Options include: min-length This option is the minimum number of steps (edges) required in the path. It should be a non-negative integer, and the default is 1. max-length This option Maximum number of steps (edges) allowed in the path. It should be a non-negative integer, and the default is unlimited. - * @returns { planBuilder.ModifyPlan } - */ -transitiveClosure(...args) { - const namer = bldrbase.getNamer(args, 'start'); - const paramdefs = [['start', [PlanExprCol, PlanColumn, types.XsString], true, false], ['end', [PlanExprCol, PlanColumn, types.XsString], true, false], ['options', [PlanTransitiveClosureOptions], false, false]]; - const checkedArgs = (namer !== null) ? - bldrbase.makeNamedArgs(namer, 'PlanModifyPlan.transitiveClosure', 2, new Set(['start', 'end', 'options']), paramdefs, args) : - bldrbase.makePositionalArgs('PlanModifyPlan.transitiveClosure', 2, false, paramdefs, args); - return new PlanModifyPlan(this, 'op', 'transitive-closure', checkedArgs); - } /** * This method searches against vector data, using a query vector, selecting and returning the top K nearest vectors from the column along with data associated with that vector, for examples, document, node, or row. Provides a client interface to a server function. See {@link http://docs.marklogic.com/ModifyPlan.prototype.annTopK|ModifyPlan.prototype.annTopK} @@ -8143,6 +8281,24 @@ annTopK(...args) { return new PlanModifyPlan(this, 'op', 'ann-top-k', checkedArgs); } +/** + * This method performs a transitive closure operation over a graph-like structure, identifying all reachable node pairs from a given start node to an end node through one or more intermediate steps. A set of (start, end) node pairs where a path exists between them with a length between minLength and maxLength, inclusive. This models the SPARQL one-or-more (+) operator, enabling recursive or chained relationships to be queried efficiently. Provides a client interface to a server function. See {@link http://docs.marklogic.com/ModifyPlan.prototype.transitiveClosure|ModifyPlan.prototype.transitiveClosure} + * @method planBuilder.ModifyPlan#transitiveClosure + * @since 4.1.0 + * @param { PlanExprColName } [start] - The column is the starting node of the traversal. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. + * @param { PlanExprColName } [end] - The column is the end node of the traversal. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. + * @param { PlanTransitiveClosureOptions } [options] - This is either a sequence of strings or an object containing keys and values for the options to this operator. Options include: min-length This option is the minimum number of steps (edges) required in the path. It should be a non-negative integer, and the default is 1. max-length This option Maximum number of steps (edges) allowed in the path. It should be a non-negative integer, and the default is unlimited. + * @returns { planBuilder.ModifyPlan } + */ +transitiveClosure(...args) { + const namer = bldrbase.getNamer(args, 'start'); + const paramdefs = [['start', [PlanExprCol, PlanColumn, types.XsString], true, false], ['end', [PlanExprCol, PlanColumn, types.XsString], true, false], ['options', [PlanTransitiveClosureOptions], false, false]]; + const checkedArgs = (namer !== null) ? + bldrbase.makeNamedArgs(namer, 'PlanModifyPlan.transitiveClosure', 2, new Set(['start', 'end', 'options']), paramdefs, args) : + bldrbase.makePositionalArgs('PlanModifyPlan.transitiveClosure', 2, false, paramdefs, args); + return new PlanModifyPlan(this, 'op', 'transitive-closure', checkedArgs); + + } } /** * AccessPlan objects have methods and inherit {@link planBuilder.ModifyPlan} methods. @@ -8568,6 +8724,18 @@ patchBuilder(...args) { patchArgs = []; return new PlanPatchBuilderBase(new PlanPatchBuilder('op', 'patch-builder', checkedArgs)); } +/** + * Create column definitions which can be used in op:from-docs. Below functions are used to create column definitions. op:add-column, op:type, op:xpath, op:expr, op:nullable, op:default, op:dimension, op:coordinate-system, op:units, op:collation. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.columnBuilder|op.columnBuilder} + * @method planBuilder#columnBuilder + * @since 4.1.0 + + * @returns { planBuilder.ColumnBuilder } + */ +columnBuilder(...args) { + bldrbase.checkMaxArity('PlanBuilder.columnBuilder', args.length, 0); + columnBuilderArgs = []; + return new PlanColumnBuilderBase(new PlanColumnBuilder('op', 'column-builder', args)); + } /** * This function creates a placeholder for a literal value in an expression or as the offset or max for a limit. The op:result function throws in an error if the binding parameter does not specify a literal value for the parameter. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.param|op.param} * @method planBuilder#param @@ -8895,6 +9063,27 @@ fromDocUris(...args) { bldrbase.makePositionalArgs('PlanBuilder.fromDocUris', 1, false, paramdefs, args); return new PlanAccessPlan(null, 'op', 'from-doc-uris', checkedArgs); + } +/** + * This function dynamically maps semi-structured data (JSON/XML) into rows and columns without deploying a TDE template. It enables ad-hoc queries similar to Virtual Template Views but with additional flexibility, such as node output and advanced column customization. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.fromDocs|op.fromDocs} + * @method planBuilder#fromDocs + * @since 4.1.0 + * @param { PlanQueryDef } [ctsQuery] - Query to select documents for row generation . The query can be a cts:query or a string as a shortcut for a cts:word-query. + * @param { XsString } [contextPath] - XPath applied to each matched document; each result becomes a row. + * @param { PlanColumnBuilder } [columnSpec] - The column definitionss create by using op:column-builder + * @param { XsString } [qualifier] - Specifies a name for qualifying the column names in place of the combination of the schema and view names. Use cases for the qualifier include self joins. Using an empty string removes all qualification from the column names. + * @param { PlanSystemColumn } [systemCol] - An optional named fragment id column returned by op:fragment-id-col. One use case for fragment ids is in joins with lexicons or document content. + * @param { PlanNamespaceBindings } [namespaces] - Namespaces prefix (key) and uri (value). + * @returns { planBuilder.AccessPlan } + */ +fromDocs(...args) { + const namer = bldrbase.getNamer(args, 'cts-query'); + const paramdefs = [['cts-query', [types.CtsQuery, types.XsString], true, false], ['context-path', [types.XsString], true, false], ['column-spec', [PlanColumnBuilder], true, false], ['qualifier', [types.XsString], false, false], ['system-col', [PlanSystemColumn], false, false], ['namespaces', [PlanNamespaceBindings], false, false]]; + const checkedArgs = (namer !== null) ? + bldrbase.makeNamedArgs(namer, 'PlanBuilder.fromDocs', 3, new Set(['cts-query', 'context-path', 'column-spec', 'qualifier', 'system-col', 'namespaces']), paramdefs, args) : + bldrbase.makePositionalArgs('PlanBuilder.fromDocs', 3, false, paramdefs, args); + return new PlanAccessPlan(null, 'op', 'from-docs', checkedArgs); + } /** * This function returns a filter definition as input for a WHERE operation. As with a cts:query or sem:store, the filter definition cannot be used in an Optic Boolean expression but, instead, must be the only argument to the WHERE call. Add a separate WHERE call to filter based on an Optic Boolean expression. The condition must be a valid simple SQL Boolean expression expressed as a string. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.sqlCondition|op.sqlCondition} @@ -9288,22 +9477,33 @@ when(...args) { } /** - * This function extracts a sequence of child nodes from a column with node values -- especially, the document nodes from a document join. The path is an XPath (specified as a string) to apply to each node to generate a sequence of nodes as an expression value. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.xpath|op.xpath} + * This function extracts a sequence of child nodes from a column with node values -- especially, the document nodes from a document join. The path is an XPath (specified as a string) to apply to each node to generate a sequence of nodes as an expression value. Sets the XPath expression in the document from which to extract content for the column created by op:add-column. It cannot be used together with op:expr, as both of them define the content of the column. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.xpath|op.xpath} * @method planBuilder#xpath * @since 2.1.1 - * @param { PlanColumnName } [column] - The name of the column from which to extract the child nodes. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function. + * @param { PlanXpathExprColName } [column] - The name of the column from which to extract the child nodes. The column can be named with a string or a column function such as op:col, op:view-col, or op:schema-col, or constructed from an expression with the op:as function.It can also be op:context to refer to the node of the current processing row. * @param { XsString } [path] - An XPath (specified as a string) to apply to each node. * @param { PlanNamespaceBindings } [namespaceBindings] - A map of namespace bindings. The keys should be namespace prefixes and the values should be namespace URIs. These namespace bindings will be added to the in-scope namespace bindings in the evaluation of the path. * @returns { Node } */ xpath(...args) { const namer = bldrbase.getNamer(args, 'column'); - const paramdefs = [['column', [PlanColumn, types.XsString], true, false], ['path', [types.XsString, PlanColumn, PlanParam], true, false], ['namespace-bindings', [PlanNamespaceBindings], false, true]]; + const paramdefs = [['column', [PlanExprCol, PlanColumn, types.XsString, PlanContextExprCall], true, false], ['path', [types.XsString, PlanColumn, PlanParam], true, false], ['namespace-bindings', [PlanNamespaceBindings], false, true]]; const checkedArgs = (namer !== null) ? bldrbase.makeNamedArgs(namer, 'PlanBuilder.xpath', 2, new Set(['column', 'path', 'namespace-bindings']), paramdefs, args) : bldrbase.makePositionalArgs('PlanBuilder.xpath', 2, false, paramdefs, args); return new types.Node('op', 'xpath', checkedArgs); + } +/** + * This helper function returns the node from the current processing row. It is to be used in op:xpath, to reference the 'current item' instead of a doc column. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.context|op.context} + * @method planBuilder#context + * @since 4.1.0 + + * @returns { planBuilder.PlanContextExprCall } + */ +context(...args) { + bldrbase.checkMaxArity('PlanBuilder.context', args.length, 0); + return new PlanContextExprCall('op', 'context', args); } /** * This function constructs a JSON document with the root content, which must be exactly one JSON object or array node. Provides a client interface to a server function. See {@link http://docs.marklogic.com/op.jsonDocument|op.jsonDocument} diff --git a/test-basic/optic-fromDocs.js b/test-basic/optic-fromDocs.js new file mode 100644 index 00000000..b8b78206 --- /dev/null +++ b/test-basic/optic-fromDocs.js @@ -0,0 +1,343 @@ +/* +* Copyright (c) 2015-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. +*/ +'use strict'; + +const should = require('should'); + +const marklogic = require('../'); +const op = marklogic.planBuilder; + +const pbb = require('./plan-builder-base'); +var testconfig = require('../etc/test-config.js'); +const execPlan = pbb.execPlan; +const getResults = pbb.getResults; +var db = marklogic.createDatabaseClient(testconfig.restWriterConnection); +const Stream = require('stream'); +const testlib = require("../etc/test-lib"); +let result = new Set(); +let uris = []; +let serverConfiguration = {}; + +describe('optic-update fromDocs tests', function() { + + this.timeout(15000); + before(function (done) { + try { + testlib.findServerConfiguration(serverConfiguration); + setTimeout(()=>{done();}, 3000); + } catch(error){ + done(error); + } + }); + + describe('fromDocs', function () { + + before(function (done) { + // Insert test documents + const testDocs = [ + { + uri: '/test/fromDocs/artist-incomplete.json', + contentType: 'application/json', + content: { + artist: { + firstName: "Charlie", + lastName: "Parker", + // birthDate is missing - will test default value + instrument: "saxophone" + // genre is missing - will test default value + } + } + }, + { + uri: '/test/fromDocs/product-widget.json', + contentType: 'application/json', + content: { + product: { + name: "Widget", + price: 25.50, + quantity: 100 + } + } + }, + { + uri: '/test/fromDocs/location-seattle.json', + contentType: 'application/json', + collections: ['fromDocs'], + content: { + location: { + city: "Seattle", + point: "47.61, -122.33", + description: "Emerald City" + } + } + }, + { + // we already have a geospatial element index for 'point' in wgs84 + // in the test-app ml-gradle project. Use that. Use 'point' to indicate location. + uri: '/test/fromDocs/location-portland.json', + contentType: 'application/json', + collections: ['fromDocs'], + content: { + location: { + city: "Portland", + point: "45.52, -122.68", + description: "City of Roses" + } + } + }, + { + uri: '/test/fromDocs/location-san-francisco.json', + contentType: 'application/json', + collections: ['fromDocs'], + content: { + location: { + city: "San Francisco", + point: "37.77, -122.42", + description: "City by the Bay" + } + } + }, + { + uri: '/test/fromDocs/location-new-york.json', + contentType: 'application/json', + collections: ['fromDocs'], + content: { + location: { + city: "New York", + point: "40.71, -74.01", + description: "The Big Apple" + } + } + }, + { + // Bob and Alice are already loaded by ml-gradle test app + uri: '/test/fromDocs/person-donald.json', + contentType: 'application/json', + content: { + person: { + name: "Donald", + summary: "Bad Donald", + embedding: [ + -1.1, + -2.2, + -3.3 + ] + } + } + } + ]; + + let readable = new Stream.Readable({objectMode: true}); + testDocs.forEach(doc => { + readable.push(doc); + uris.push(doc.uri); + }); + readable.push(null); + + db.documents.writeAll(readable, { + onCompletion: () => done() + }); + }); + + after(function (done) { + if (uris.length > 0) { + db.documents.remove(uris) + .result(() => done()) + .catch(done); + } else { + done(); + } + }); + + it('fromDocs basic', function (done) { + const plan = op.fromDocs( + op.cts.wordQuery('Coltrane'), + '/musician', + op.columnBuilder() + .addColumn('lastName').xpath('./lastName').type('string') + .addColumn('firstName').xpath('./firstName').type('string') + .addColumn('dob').xpath('./dob').type('string') + .addColumn('instrument').xpath('./instrument').type('string'), + "MyView" + ); + execPlan(plan).then(function (response) { + const output = getResults(response); + output.length.should.equal(1); + output[0]['MyView.lastName'].value.should.equal('Coltrane'); + output[0]['MyView.firstName'].value.should.equal('John'); + output[0]['MyView.dob'].value.should.equal('1926-09-23'); + output[0]['MyView.instrument'].value.should.equal('saxophone'); + done(); + }).catch(done); + }); + + it('fromDocs with default', function (done) { + const plan = op.fromDocs( + op.cts.wordQuery('Parker'), + '/artist', + op.columnBuilder() + .addColumn('lastName').xpath('./lastName').type('string').collation('http://marklogic.com/collation/') + .addColumn('firstName').xpath('./firstName').type('string').collation('http://marklogic.com/collation/') + .addColumn('birthDate').xpath('./birthDate').type('string').default('Unknown') + .addColumn('instrument').xpath('./instrument').type('string') + .addColumn('genre').xpath('./genre').type('string').default('Jazz'), + "ArtistView" + ); + execPlan(plan).then(function (response) { + const output = getResults(response); + output.length.should.equal(1); + output[0]['ArtistView.lastName'].value.should.equal('Parker'); + output[0]['ArtistView.firstName'].value.should.equal('Charlie'); + output[0]['ArtistView.birthDate'].value.should.equal('Unknown'); + output[0]['ArtistView.instrument'].value.should.equal('saxophone'); + output[0]['ArtistView.genre'].value.should.equal('Jazz'); + done(); + }).catch(done); + }); + + it('fromDocs with expr', function (done) { + const plan = op.fromDocs( + op.cts.wordQuery('Widget'), + '/product', + op.columnBuilder() + .addColumn('name').xpath('./name').type('string') + .addColumn('price').xpath('./price').type('decimal') + .addColumn('quantity').xpath('./quantity').type('integer') + .addColumn('twoToTheThirdPower').nullable(true) + .expr(op.math.pow(2, 3)) + .type('integer'), + "ProductView" + ); + execPlan(plan).then(function (response) { + const output = getResults(response); + output.length.should.equal(1); + output[0]['ProductView.name'].value.should.equal('Widget'); + output[0]['ProductView.price'].value.should.equal(25.50); + output[0]['ProductView.quantity'].value.should.equal(100); + output[0]['ProductView.twoToTheThirdPower'].value.should.equal(8); + done(); + }).catch(done); + }); + + it('fromDocs with op.context() and op.xpath()', function (done) { + const plan = op.fromDocs( + op.cts.wordQuery('Widget'), + '/product', + op.columnBuilder() + .addColumn('name').xpath('./name').type('string') + .addColumn('quantity').xpath('./quantity').type('integer') + .addColumn('price').xpath('./price').type('decimal') + .addColumn('totalCost').nullable(true) + .expr(op.multiply( + op.xs.decimal(op.xpath(op.context(), './price')), + op.xs.integer(op.xpath(op.context(), './quantity')) + ) + ) + .type('decimal'), + "ProductView" + ); + execPlan(plan).then(function (response) { + const output = getResults(response); + output.length.should.equal(1); + output[0]['ProductView.name'].value.should.equal('Widget'); + output[0]['ProductView.price'].value.should.equal(25.50); + output[0]['ProductView.quantity'].value.should.equal(100); + output[0]['ProductView.totalCost'].value.should.equal(2550); + done(); + }).catch(done); + }); + + it('fromDocs with geospatial query', function (done) { + + const portlandPoint = op.cts.point(45.52, -122.68); + const searchRadius = 650; // miles + // geospatial element index is defined for 'point' in wgs84 + const plan = op.fromDocs( + op.cts.collectionQuery('fromDocs'), + '/location', + op.columnBuilder() + .addColumn('city').xpath('./city').type('string') + .addColumn('location-wgs84').xpath('./point').type('point').coordinateSystem('wgs84') + .addColumn('description').xpath('./description').type('string'), + "LocationView" + ) + .where( + op.cts.jsonPropertyGeospatialQuery + ( + 'point', + op.cts.circle(searchRadius, portlandPoint), + ['coordinate-system=wgs84'] + ) + ); + + execPlan(plan).then(function (response) { + const output = getResults(response); + output.length.should.be.equal(3); + const cities = output.map(row => row['LocationView.city'].value); + cities.should.containEql('Portland'); + cities.should.containEql('Seattle'); + cities.should.containEql('San Francisco'); + cities.should.not.containEql('New York'); + done(); + }).catch(done); + }); + + it('fromDocs with vector and dimension', function (done) { + + const testVector = op.vec.vector([1,2,3]); + const plan = op.fromDocs( + op.cts.wordQuery('*'), + '/person', + op.columnBuilder() + .addColumn('name').xpath('./name').type('string') + .addColumn('summary').xpath('./summary').type('string') + .addColumn('embedding').xpath('vec:vector(./embedding)').type('vector').dimension(3) + .addColumn('cosineDistance').nullable(true).expr( + op.vec.cosineDistance + ( + op.vec.vector(testVector), + op.vec.vector(op.xpath(op.context(), './embedding')) + ) + ).type('double') + .addColumn('euclideanDistance').nullable(true).expr( + op.math.trunc( + op.vec.euclideanDistance + ( + op.vec.vector(testVector), + op.vec.vector(op.xpath(op.context(), './embedding')) + ) + , 4) + ).type('double') + , + "PersonView" + ).where( + op.lt( + op.vec.cosineDistance + ( + op.vec.vector(testVector), + op.viewCol('PersonView', 'embedding') + ) + , + op.xs.double(0.5) + ) + ) + .orderBy(op.viewCol('PersonView', 'euclideanDistance')); + + execPlan(plan).then(function (response) { + const output = getResults(response); + output.length.should.be.equal(2); + const distances = output.map(row => row['PersonView.euclideanDistance'].value); + distances[0].should.be.approximately(0.3741, 0.0001); // Alice is .3741 from testVector + distances[1].should.be.approximately(12.1466, 0.0001); // Bob is 12.1466 from testVector + const names = output.map(row => row['PersonView.name'].value); + names.should.containEql('Alice'); + names.should.containEql('Bob'); + names.should.not.containEql('Donald'); + done(); + }).catch(done); + }); + + + }); +}); \ No newline at end of file