Skip to content

Commit 1e270bf

Browse files
committed
edit: multikey search passing tests, limit fix
breaking api change: limit will no longer accept a `end` key, but rather will work as Arango and god intended, with a `count` key. in effect though the package claimed to be offering start and end, end was actually just a misnamed variable, and was passed straight to ArangoSearch's `COUNT` parameter. This means that the values passed won't change, but the key name will have to in order to try to adhere to Arango's specifications. Apologies to those for whom this breaking change will come with only a patch version change
1 parent a8ca30b commit 1e270bf

File tree

5 files changed

+94
-60
lines changed

5 files changed

+94
-60
lines changed

README.md

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,12 @@ const queryObject = {
151151
"query": "+'query string' -for +parseQuery ?to parse"
152152
}
153153

154-
const limit = {start:1, end: 20}
154+
const limit = {start:0, count: 20}
155155

156156
const aqlQuery = buildAQL(queryObject, limit)
157157

158158
// ... const cursor = await db.query(aqlQuery)
159-
// ... const cursor = await db.query(buildAQL(queryObject, {start:21, end:40})
159+
// ... const cursor = await db.query(buildAQL(queryObject, {start:20, count:20})
160160
```
161161

162162
Generate documenation with `yarn doc && serve docs/` or see more examples in
@@ -210,18 +210,18 @@ the following shape:
210210

211211
#### limit
212212
an object with the following keys:
213-
- `start` (integer), 1-based result pagination lower bound.
214-
- `end` (integer) 1-based result pagination upper bound
215-
i.e. to bring back up to the first 20 results
213+
- `start` (integer) 0-based result pagination lower bound.
214+
- `count` (integer) total number of results to return.
215+
i.e. to bring back up to the first 10 results
216216
```json
217-
{start:1, end:20}
217+
{"start":0, "count":10}
218218
```
219219
and the next page would be
220220
```json
221-
{start:21, end:40}
221+
{"start":10, "count":10}
222222
```
223223

224-
#### `query` Example
224+
#### `query` example
225225
```json
226226
{
227227
"view": "the_arango-search_view-name",
@@ -251,22 +251,10 @@ and the next page would be
251251

252252
___
253253

254-
#### `limit` object
255-
the second parameter passed to `buildAQL(query, limit)`
256-
257-
optional, default `{start:1, end: 20}`, **start** (optional | default: 1): the
258-
1-based inclusive starting index of the result set to return.
259254

260255
**end** (optional: default: 20): the 1-based inclusive ending index of the
261256
result set to result
262257

263-
#### `limit` Example
264-
```json
265-
{
266-
"start": 21,
267-
"end": 40
268-
}
269-
```
270258

271259
___
272260
### boolean search logic

src/index.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,22 @@ import { buildFilters } from './filter'
55
export { buildFilters } from './filter'
66
export { parseQuery } from './parse'
77

8-
/** @returns an AQL query object. See @param query for
9-
* details on required values. @param query.terms accepts
10-
* either a string to be parsed or an array of terms. @param limit is an object with keys `start` default 0, and `end` default 20.
11-
* */
8+
/** @returns an AQL query object. See @param query for details on required
9+
* values. @param query.terms accepts either a string to be parsed or an array
10+
* of terms.
11+
*
12+
* ! NOTE: v0.1.1 introduced a breaking change to adhere to the ArangoSearch
13+
* spec. Please refer to
14+
* <https://www.arangodb.com/docs/stable/aql/operations-limit.html> @param
15+
* limit is an object with keys `start` @default 0, and `count` @default 20 */
1216
export function buildAQL(
1317
query: query,
14-
limit: any = { start: 0, end: 20 },
18+
limit: any = { start: 0, count: 20 },
1519
): any {
1620
validateQuery(query)
21+
/* unify query.key */
22+
query.key = query.key ? query.key : ['text']
23+
query.key = typeof query.key == 'string' ? [query.key] : query.key
1724

1825
const SEARCH = buildSearch(query)
1926
const FILTER = query.filters && buildFilters(query.filters)

src/lib/structs.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@ export interface query {
1616
* */
1717
terms: term[] | string
1818
/**
19-
* the name of the document key to search, currently must be the same across
20-
* all documents. @default "text"
19+
* the name(s) of the document key to search. @default "text"
2120
* */
22-
key?: string
21+
key?: string[] | string
2322
/**
2423
* a list of @filter interfaces. All filters are implicitly AND'ed together.
2524
* */

src/search.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export function buildSearch(query: query): any {
77
query.terms =
88
typeof query.terms == 'string' ? parseQuery(query.terms) : query.terms
99

10+
query.key = typeof query.key == 'string' ? [query.key] : query.key
11+
1012
/* build boolean pieces */
1113
let ANDS = buildOps(query.collections, query.terms, '+', query.key)
1214
let ORS = buildOps(query.collections, query.terms, '?', query.key)
@@ -41,7 +43,7 @@ function buildOps(
4143
collections: collection[],
4244
terms: term[],
4345
op: string,
44-
key: string[] | string = 'text',
46+
key: string[],
4547
): any {
4648
const opWord: string = op == '+' ? ' AND ' : ' OR '
4749

@@ -65,7 +67,7 @@ function buildOps(
6567
function buildPhrases(
6668
phrases: term[],
6769
collections: collection[],
68-
key: string[] | string,
70+
key: string[],
6971
opWord: string,
7072
): any {
7173
if (!phrases.length) return undefined
@@ -79,18 +81,23 @@ function buildPhrases(
7981
function buildPhrase(
8082
phrase: term,
8183
collections: collection[],
82-
key: string[] | string,
84+
key: string[],
8385
): any {
84-
const phrases = collections.map((coll) => {
85-
return aql`PHRASE(doc.${key}, ${phrase.val.slice(1, -1)}, ${coll.analyzer})`
86-
})
86+
const phrases = []
87+
key.forEach((k: string) =>
88+
collections.forEach((coll) =>
89+
phrases.push(
90+
aql`PHRASE(doc.${k}, ${phrase.val.slice(1, -1)}, ${coll.analyzer})`,
91+
),
92+
),
93+
)
8794
return aql`(${aql.join(phrases, ' OR ')})`
8895
}
8996

9097
function buildTokens(
9198
tokens: term[],
9299
collections: collection[],
93-
key: string[] | string,
100+
key: string[],
94101
): any {
95102
if (!tokens.length) return
96103

@@ -110,12 +117,17 @@ function buildTokens(
110117
tokens: term[],
111118
op: string,
112119
analyzer: string,
113-
key: string[] | string,
120+
key: string[],
114121
) => {
115-
return aql`
122+
return aql.join(
123+
key.map(
124+
(k) => aql`
116125
ANALYZER(
117126
TOKENS(${tokens}, ${analyzer})
118-
${aql.literal(op)} IN doc.${key}, ${analyzer})`
127+
${aql.literal(op)} IN doc.${k}, ${analyzer})`,
128+
),
129+
' OR ',
130+
)
119131
}
120132

121133
let remapped = []

tests/search.ts

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,37 @@ describe('search.ts', () => {
88

99
it(`should return SEARCH true
1010
when terms: is an empty string or array`, () => {
11-
1211
/* empty string */
13-
let empty_string_query = { view: 'search_view', collections: [ { name: 'coll', analyzer: 'text_en' } ], terms: '' }
12+
let empty_string_query = {
13+
view: 'search_view',
14+
collections: [{ name: 'coll', analyzer: 'text_en' }],
15+
terms: '',
16+
}
1417
const builtSearch = buildSearch(empty_string_query)
1518

1619
expect(builtSearch).to.be.an('object')
1720
expect(Object.keys(builtSearch.bindVars)).to.have.length(2)
1821
expect(builtSearch.bindVars.value0).to.equal(true)
19-
expect(builtSearch.bindVars.value1).to.deep.equal({ collections: [ 'coll' ] })
22+
expect(builtSearch.bindVars.value1).to.deep.equal({ collections: ['coll'] })
2023

2124
/* empty array */
22-
let empty_array_query = { view: 'search_view', collections: [ { name: 'coll', analyzer: 'text_en' } ], terms: [] }
25+
let empty_array_query = {
26+
view: 'search_view',
27+
collections: [{ name: 'coll', analyzer: 'text_en' }],
28+
terms: [],
29+
}
2330
const builtSearch_from_array = buildSearch(empty_array_query)
2431

2532
expect(builtSearch_from_array.bindVars.value0).to.equal(true)
2633
})
2734

2835
it(`should handle single phrase queries`, () => {
29-
const query = { view: 'search_view', collections: [ { name: 'coll', analyzer: 'text_en' } ], terms: '"complex phrase"' }
36+
const query = {
37+
view: 'search_view',
38+
collections: [{ name: 'coll', analyzer: 'text_en' }],
39+
terms: '"complex phrase"',
40+
key: ['text'],
41+
}
3042
const builtSearch = buildSearch(query)
3143

3244
expect(builtSearch.query).to.equal(`
@@ -41,7 +53,12 @@ describe('search.ts', () => {
4153

4254
it(`should return an array of aql objects
4355
when a complex query is passed`, () => {
44-
const query = { view: 'search_view', collections: [ { name: 'coll', analyzer: 'text_en' } ], terms: '-a +"query string" ?token' }
56+
const query = {
57+
view: 'search_view',
58+
collections: [{ name: 'coll', analyzer: 'text_en' }],
59+
terms: '-a +"query string" ?token',
60+
key: ['text'],
61+
}
4562
const builtSearch = buildSearch(query)
4663

4764
expect(Object.keys(builtSearch.bindVars)).to.have.length(7)
@@ -50,7 +67,9 @@ describe('search.ts', () => {
5067
expect(builtSearch.bindVars.value2).to.deep.equal('text_en')
5168
expect(builtSearch.bindVars.value4).to.deep.equal(1)
5269
expect(builtSearch.bindVars.value5).to.deep.equal('a')
53-
expect(builtSearch.bindVars.value6).to.deep.equal({ collections: [ query.collections[ 0 ].name ] })
70+
expect(builtSearch.bindVars.value6).to.deep.equal({
71+
collections: [query.collections[0].name],
72+
})
5473
expect(builtSearch.query).to.equal(`
5574
SEARCH
5675
(PHRASE(doc.@value0, @value1, @value2)) OR ((PHRASE(doc.@value0, @value1, @value2)) AND MIN_MATCH(
@@ -74,11 +93,14 @@ describe('search.ts', () => {
7493
it('should return an array of aql objects', () => {
7594
const query = {
7695
view: 'search_view',
77-
collections: [ {
78-
name: 'coll',
79-
analyzer: 'text_en'
80-
} ],
81-
terms: '+mandatory -exclude ?"optional phrase"'
96+
collections: [
97+
{
98+
name: 'coll',
99+
analyzer: 'text_en',
100+
},
101+
],
102+
terms: '+mandatory -exclude ?"optional phrase"',
103+
key: ['text'],
82104
}
83105
const builtSearch = buildSearch(query)
84106
expect(builtSearch.query).to.equal(`
@@ -108,11 +130,14 @@ describe('search.ts', () => {
108130
it('should handle exclude phrase', () => {
109131
const query = {
110132
view: 'search_view',
111-
collections: [ {
112-
name: 'coll',
113-
analyzer: 'text_en'
114-
} ],
115-
terms: '-"exclude"'
133+
collections: [
134+
{
135+
name: 'coll',
136+
analyzer: 'text_en',
137+
},
138+
],
139+
terms: '-"exclude"',
140+
key: ['text'],
116141
}
117142
const builtSearch = buildSearch(query)
118143
expect(builtSearch.query).to.equal(`
@@ -130,11 +155,14 @@ describe('search.ts', () => {
130155
it('should handle exclude token', () => {
131156
const query = {
132157
view: 'search_view',
133-
collections: [ {
134-
name: 'coll',
135-
analyzer: 'text_en'
136-
} ],
137-
terms: '-exclude'
158+
collections: [
159+
{
160+
name: 'coll',
161+
analyzer: 'text_en',
162+
},
163+
],
164+
terms: '-exclude',
165+
key: ['text'],
138166
}
139167
const builtSearch = buildSearch(query)
140168
expect(builtSearch.bindVars.value0).to.equal('exclude')

0 commit comments

Comments
 (0)