Skip to content

Commit addae66

Browse files
committed
MLE-15715 : Add tests for new vector functions in Node Client Api
1 parent 7533fdf commit addae66

File tree

5 files changed

+194
-71
lines changed

5 files changed

+194
-71
lines changed

lib/plan-builder-base.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ function castArg(arg, funcName, paramName, argPos, paramTypes) {
7070
} else if (arg instanceof Number || arg instanceof Boolean || arg instanceof String) {
7171
arg = arg.valueOf();
7272
} else if (arg instanceof types.ServerType) {
73+
if(arg._ns === 'vec'){
74+
return arg._args;
75+
}
7376
throw new Error(
7477
`${argLabel(funcName, paramName, argPos)} must have type ${typeLabel(paramTypes)}`
7578
);
@@ -122,11 +125,11 @@ function castArg(arg, funcName, paramName, argPos, paramTypes) {
122125
const value = arg[key];
123126
switch(key) {
124127
case 'scoreMethod':
125-
if (['logtfidf', 'logtf', 'simple', 'zero', 'random', 'bm25'].includes(value)) {
128+
if (['logtfidf', 'logtf', 'simple', 'bm25'].includes(value)) {
126129
return true;
127130
}
128131
throw new Error(
129-
`${argLabel(funcName, paramName, argPos)} can only be 'logtfidf', 'logtf', 'simple', 'zero', 'random' or 'bm25'`
132+
`${argLabel(funcName, paramName, argPos)} can only be 'logtfidf', 'logtf', 'simple' or 'bm25'`
130133
);
131134
case 'qualityWeight':
132135
if (typeof value === 'number' || value instanceof Number) {

lib/plan-builder-generated.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8695,7 +8695,7 @@ fromSQL(...args) {
86958695
* @since 2.1.1
86968696
* @param { PlanSearchQuery } [query] - Qualifies and establishes the scores for a set of documents. The query can be a cts:query or a string as a shortcut for a cts:word-query.
86978697
* @param { XsString } [qualifierName] - Specifies a name for qualifying the column names.
8698-
* @param { PlanSearchOption } [option] - Similar to the options of cts:search, supplies the 'scoreMethod' key with a value of 'logtfidf', 'logtf', 'simple', 'zero', 'random', or 'bm25' to specify the method for assigning a score to matched documents or supplies the 'qualityWeight' key with a numeric value to specify a multiplier for the quality contribution to the score. Specify a value between 0 (exclusive) and 1 (inclusive) for bm25LengthWeight if 'bm25' scoring method is used.
8698+
* @param { PlanSearchOption } [option] - Similar to the options of cts:search, supplies the 'scoreMethod' key with a value of 'logtfidf', 'logtf', 'simple', or 'bm25' to specify the method for assigning a score to matched documents or supplies the 'qualityWeight' key with a numeric value to specify a multiplier for the quality contribution to the score. Specify a value between 0 (exclusive) and 1 (inclusive) for bm25LengthWeight if 'bm25' scoring method is used.
86998699
* @returns { planBuilder.AccessPlan }
87008700
*/
87018701
fromSearchDocs(...args) {
@@ -8714,7 +8714,7 @@ fromSearchDocs(...args) {
87148714
* @param { PlanSearchQuery } [query] - Qualifies and establishes the scores for a set of documents. The query can be a cts:query or a string as a shortcut for a cts:word-query. The fragments are not filtered to ensure they match the query, but instead selected in the same manner as "unfiltered" cts:search operations.
87158715
* @param { PlanExprColName } [columns] - Specifies which of the available columns to include in the rows. The available columns include the metrics for relevance ('confidence', 'fitness', 'quality', and 'score') and fragmentId for the document identifier. By default, the rows have the fragmentId and score columns. To rename a column, use op:as specifying the new name for an op:col with the old name.
87168716
* @param { XsString } [qualifierName] - Specifies a name for qualifying the column names.
8717-
* @param { PlanSearchOption } [option] - Similar to the options of cts:search, supplies the 'scoreMethod' key with a value of 'logtfidf', 'logtf', 'simple', 'zero', 'random', or 'bm25' to specify the method for assigning a score to matched documents or supplies the 'qualityWeight' key with a numeric value to specify a multiplier for the quality contribution to the score. Specify a value between 0 (exclusive) and 1 (inclusive) for bm25LengthWeight if 'bm25' scoring method is used.
8717+
* @param { PlanSearchOption } [option] - Similar to the options of cts:search, supplies the 'scoreMethod' key with a value of 'logtfidf', 'logtf', 'simple' or 'bm25' to specify the method for assigning a score to matched documents or supplies the 'qualityWeight' key with a numeric value to specify a multiplier for the quality contribution to the score. Specify a value between 0 (exclusive) and 1 (inclusive) for bm25LengthWeight if 'bm25' scoring method is used.
87188718
* @returns { planBuilder.AccessPlan }
87198719
*/
87208720
fromSearch(...args) {

test-basic/optic-vector.js

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright © 2024 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
'use strict';
17+
18+
const should = require('should');
19+
20+
const marklogic = require('../');
21+
const p = marklogic.planBuilder;
22+
23+
const pbb = require('./plan-builder-base');
24+
const execPlan = pbb.execPlan;
25+
const getResults = pbb.getResults;
26+
const assert = require('assert');
27+
const testlib = require("../etc/test-lib");
28+
let serverConfiguration = {};
29+
const testPlan = pbb.testPlan;
30+
31+
describe('tests for new vector fucntions.', function() {
32+
before(function (done) {
33+
this.timeout(6000);
34+
try {
35+
testlib.findServerConfiguration(serverConfiguration);
36+
setTimeout(()=>{
37+
if(serverConfiguration.serverVersion < 12) {
38+
this.skip();
39+
}
40+
done();
41+
}, 3000);
42+
} catch(error){
43+
done(error);
44+
}
45+
46+
});
47+
48+
it('vec.add', function(done) {
49+
const vec1 = p.vec.vector([0.000000000001]);
50+
const vec2 = p.vec.vector([0.000000000001, 0.000000000002]);
51+
testPlan([""],p.vec.add(p.vec.subvector(vec1,0),p.vec.subvector(vec2,1)))
52+
.then(function(response) {
53+
assert(response.rows[0].t.value[0][0] =='1e-12')
54+
assert(response.rows[0].t.value[1][0]=='2e-12')
55+
done();
56+
}).catch(error => done(error));
57+
});
58+
59+
it('vec.subtract', function(done) {
60+
const vec1 = p.vec.vector([0.000000000002]);
61+
const vec2 = p.vec.vector([0.000000000001]);
62+
testPlan([""],p.vec.subtract(p.vec.subvector(vec1,0),p.vec.subvector(vec2,0)))
63+
.then(function(response) {
64+
assert(response.rows[0].t.value[0][0] =='2e-12')
65+
assert(response.rows[0].t.value[1][0]=='1e-12')
66+
done();
67+
}).catch(error => done(error));
68+
});
69+
70+
it('vec.base64decode', function(done) {
71+
72+
const vec1 = p.vec.vector([0.002]);
73+
testPlan([""],p.vec.subvector(p.vec.base64Decode(p.vec.base64Encode(p.vec.subvector(vec1,0))),0))
74+
.then(function(response) {
75+
assert(response.rows[0].t.value[0][0] =='0.002')
76+
done();
77+
}).catch(error => done(error));
78+
});
79+
80+
it('vec.base64Encode', function(done) {
81+
const vec1 = p.vec.vector([0.002]);
82+
testPlan([""],p.vec.base64Encode(p.vec.subvector(vec1,0)))
83+
.then(function(response) {
84+
assert(response.rows[0].t.value =='FYT6NiFJhGw=AQAAAA==AAAAAA==bxIDOw==')
85+
done();
86+
}).catch(error => done(error));
87+
});
88+
89+
it('vec.cosineSimilarity', function(done) {
90+
const vec1 = p.vec.vector([1, 2, 3])
91+
const vec2 = p.vec.vector([4, 5, 6,7])
92+
93+
testPlan([""],p.vec.cosineSimilarity(p.vec.subvector(vec1,0),p.vec.subvector(vec2,1)))
94+
.then(function(response) {
95+
assert(response.rows[0].t.value != null);
96+
done();
97+
}).catch(error => done(error));
98+
});
99+
100+
it('vec.dimension', function(done) {
101+
102+
testPlan([""],p.vec.dimension(p.vec.vector([1, 2, 3])))
103+
.then(function(response) {
104+
assert(response.rows[0].t.value == 3);
105+
done();
106+
}).catch(error => done(error));
107+
});
108+
109+
it('vec.dotproduct', function(done) {
110+
const vec1 = p.vec.vector([1, 2, 3])
111+
const vec2 = p.vec.vector([4, 5, 6,7])
112+
113+
testPlan([""],p.vec.cosineSimilarity(p.vec.subvector(vec1,0),p.vec.subvector(vec2,1)))
114+
.then(function(response) {
115+
assert(response.rows[0].t.value == '0.968329608440399');
116+
done();
117+
}).catch(error => done(error));
118+
});
119+
120+
it('vec.euclideanDistance', function(done) {
121+
const vec1 = p.vec.vector([1, 2, 3])
122+
const vec2 = p.vec.vector([4, 5, 6,7])
123+
124+
testPlan([""],p.vec.euclideanDistance(p.vec.subvector(vec1,0,2),p.vec.subvector(vec2,1,2)))
125+
.then(function(response) {
126+
assert(response.rows[0].t.value == '5.65685415267944');
127+
done();
128+
}).catch(error => done(error));
129+
});
130+
131+
it('vec.get', function(done) {
132+
133+
testPlan([""],p.vec.get(p.vec.vector([1, 2, 3]),1))
134+
.then(function(response) {
135+
assert(response.rows[0].t.value == 2);
136+
done();
137+
}).catch(error => done(error));
138+
});
139+
140+
it('vec.magnitude', function(done) {
141+
142+
testPlan([""],p.vec.magnitude(p.vec.vector([1, 2, 3])))
143+
.then(function(response) {
144+
assert(response.rows[0].t.value == '3.74165749549866');
145+
done();
146+
}).catch(error => done(error));
147+
});
148+
149+
it('vec.normalize', function(done) {
150+
151+
testPlan([""],(p.vec.normalize(p.vec.subvector(p.vec.vector([1,2]),1))))
152+
.then(function(response) {
153+
assert(response.rows[0].t.value == 2);
154+
done();
155+
}).catch(error => done(error));
156+
});
157+
158+
it('vec.vectorScore', function(done) {
159+
const vec1 = p.vec.vector([1, 2, 3])
160+
testPlan([""],(p.vec.vectorScore(24684,0.1,0.1)))
161+
.then(function(response) {
162+
assert(response.rows[0].t.value == 24687);
163+
done();
164+
}).catch(error => done(error));
165+
});
166+
});

test-basic/plan-builder-generated.js

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -395,18 +395,14 @@ describe('plan builder', function() {
395395
should(String(getResult(response).value).replace(/^ /, '')).equal('10:09:08:00');
396396
done();
397397
}).catch(done);
398-
});
398+
});
399399
it('fn.head#1', function(done) {
400400
testPlan([[p.xs.string("a"), p.xs.string("b"), p.xs.string("c")]], p.fn.head(p.col("1")))
401401
.then(function(response) {
402-
if(serverConfiguration.serverVersion < 12){
403-
should(String(getResult(response).value).replace(/^ /, '')).equal('a');
404-
} else {
405-
should(getResult(response).value).eql(null);
406-
}
402+
should(String(getResult(response).value).replace(/^ /, '')).equal('a');
407403
done();
408404
}).catch(done);
409-
});
405+
});
410406
it('fn.hoursFromDateTime#1', function(done) {
411407
testPlan([p.xs.dateTime("2016-01-02T10:09:08Z")], p.fn.hoursFromDateTime(p.col("1")))
412408
.then(function(response) {
@@ -1744,31 +1740,35 @@ describe('plan builder', function() {
17441740
this.skip();
17451741
}
17461742
testPlan([p.xs.string("abc")], p.vec.base64Decode(p.col("1")))
1747-
.then(function(response) {
1748-
should(String(getResult(response).value).replace(/^ /, '')).equal(null);
1743+
.then(function(response) {
1744+
should(String(getResult(response).value).replace(/^ /, '')).equal("abc");
17491745
done();
17501746
}).catch(done);
1751-
});
1747+
});
17521748
it('vec.vectorScore#2', function(done) {
17531749
if(serverConfiguration.serverVersion < 12) {
17541750
this.skip();
17551751
}
1756-
testPlan([p.xs.unsignedInt(1), p.xs.double(1.2)], p.vec.vectorScore(p.col("1"), p.col("2")))
1757-
.then(function(response) {
1758-
should(String(getResult(response).value).replace(/^ /, '')).equal('8');
1752+
// Updating below test since server throws Error : VEC-VECTORSCORE: vec:vector-score(1, 2) --
1753+
// Invalid argument passed to vector scoring function: arg2 invalid: Similarity must be between -1.0 and 1.0
1754+
testPlan([p.xs.unsignedInt(1), p.xs.double(1.2)], p.vec.vectorScore(p.col("1"), 0.75))
1755+
.then(function(response) {
1756+
should(String(getResult(response).value).replace(/^ /, '')).equal('1');
17591757
done();
17601758
}).catch(done);
1761-
});
1759+
});
17621760
it('vec.vectorScore#3', function(done) {
17631761
if(serverConfiguration.serverVersion < 12) {
17641762
this.skip();
17651763
}
1766-
testPlan([p.xs.unsignedInt(1), p.xs.double(1.2), p.xs.double(1.2)], p.vec.vectorScore(p.col("1"), p.col("2"), p.col("3")))
1767-
.then(function(response) {
1768-
should(String(getResult(response).value).replace(/^ /, '')).equal('10');
1764+
// Updating below test since server throws Error :arg2 invalid:Similarity must be between -1.0 and 1.0
1765+
//arg3 invalid: Similarity weight must be between 0.0 and 1.0
1766+
testPlan([p.xs.unsignedInt(1), p.xs.double(1.2), p.xs.double(1.2)], p.vec.vectorScore(p.col("1"), 0.75, 0.75))
1767+
.then(function(response) {
1768+
should(String(getResult(response).value).replace(/^ /, '')).equal('2');
17691769
done();
17701770
}).catch(done);
1771-
});
1771+
});
17721772
it('xdmp.add64#2', function(done) {
17731773
testPlan([p.xs.unsignedLong(123), p.xs.unsignedLong(456)], p.xdmp.add64(p.col("1"), p.col("2")))
17741774
.then(function(response) {

test-basic/plan-search.js

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ describe('search', function() {
6565
}).catch(error => done(error));
6666
});
6767
it('columns', function(done) {
68+
this.timeout(5000)
6869
execPlan(
6970
p.fromSearch(p.cts.jsonPropertyValueQuery('instrument', 'trumpet'),
7071
['fragmentId', p.col('confidence'), 'fitness', p.as('weight', p.col('quality'))])
@@ -183,7 +184,7 @@ describe('search', function() {
183184
// console.log(JSON.stringify(output, null, 2));
184185
});
185186

186-
describe('tests for new scoring methods - bm25, random and zero using fromSearch.', function() {
187+
describe('tests for new scoring method - bm25', function() {
187188
before(function (done) {
188189
if(serverConfiguration.serverVersion < 12) {
189190
this.skip();
@@ -228,33 +229,9 @@ describe('search', function() {
228229
}
229230
});
230231

231-
it('should search documents with zero scoring method', function(done) {
232-
execPlan(
233-
p.fromSearch(p.cts.jsonPropertyValueQuery('instrument', 'trumpet'),
234-
['score', 'quality'], null, { scoreMethod: 'zero'})
235-
).then(function(response) {
236-
assert(response.columns != null)
237-
assert(response.rows != null)
238-
for(let i=0; i<response.rows.length; i++){
239-
assert(response.rows[i].score.value == 0)
240-
}
241-
done();
242-
}).catch(error => done(error));
243-
});
244-
245-
it('should search documents with random scoring method', function(done) {
246-
execPlan(
247-
p.fromSearch(p.cts.jsonPropertyValueQuery('instrument', 'trumpet'),
248-
['score', 'quality'], null, { scoreMethod: 'random'})
249-
).then(function(response) {
250-
assert(response.columns != null)
251-
assert(response.rows != null)
252-
done();
253-
}).catch(error => done(error));
254-
});
255232
});
256233

257-
describe('tests for new scoring methods - bm25, random and zero using fromSearchDocs.', function() {
234+
describe('tests for new scoring method - bm25', function() {
258235
before(function (done) {
259236
if(serverConfiguration.serverVersion < 12) {
260237
this.skip();
@@ -295,28 +272,5 @@ describe('search', function() {
295272
done();
296273
}
297274
});
298-
299-
it('should search documents with zero scoring method', function(done) {
300-
execPlan(
301-
p.fromSearchDocs('Armstrong', null, { scoreMethod: 'zero'})
302-
).then(function(response) {
303-
assert(response.columns != null)
304-
assert(response.rows != null)
305-
for(let i=0; i<response.rows.length; i++){
306-
assert(response.rows[i].score.value == 0)
307-
}
308-
done();
309-
}).catch(error => done(error));
310-
});
311-
312-
it('should search documents with random scoring method', function(done) {
313-
execPlan(
314-
p.fromSearchDocs('Armstrong', null, { scoreMethod: 'random'})
315-
).then(function(response) {
316-
assert(response.columns != null)
317-
assert(response.rows != null)
318-
done();
319-
}).catch(error => done(error));
320-
});
321275
});
322276
});

0 commit comments

Comments
 (0)