Skip to content

Commit 8e76189

Browse files
authored
Merge pull request #6 from HP4k1h5/v0.1.1
V0.1.1
2 parents fa846e3 + 58d486c commit 8e76189

13 files changed

+851
-450
lines changed

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# CHANGELOG
2+
3+
- v0.1.1
4+
- ❌ Breaking change!
5+
`buildAQL()`'s `limit` parameter no longer accepts key
6+
`end`, which has been renamed, per Arango spec, to `count`. The functionality
7+
remains the same, which is why the patch bump. Please accept my apologies
8+
for the un-semver approach. I have a personal philosophy that v0.X.X is
9+
not really subject to the standard rules of semver, but I will do my best
10+
to not present further breaking changes without upping at least the minor
11+
version. It's important for any user of the library to have a version that
12+
will be more compatible with all future versions going forward.
13+
- 🔑🗝 multi-key search
14+
this can be useful if you
15+
have multiple fields with textual information. Theoretically, each
16+
chapter of a book could be stored on its own key. Or a document could
17+
have be translated into several languages, each stored on its own key.
18+
- **There are two ways to provide multiple keys to relevant functions.**
19+
1) `query.key` now accepts in addition to a string value, an array of
20+
strings over which the query is to be run. All keys listed here will be
21+
run combined with all collections provided to `query.collections`
22+
**unless** a collection has a `keys` property of its own, in which case
23+
**only** those keys are searched against.
24+
2) `query.collections[].keys` is an optional array of key names that are
25+
indexed by `query.collections[].analyzer`. **Note** It is important that
26+
any key listed in `query.collections[].keys` be indexed by the analyzer
27+
as it will impact results if such a key does not exist.
28+
29+

README.md

Lines changed: 132 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,51 @@
11
# AQLqueryBuilder.js
22
> a typescript query builder for [arangodb](https://www.arangodb.com)'s [ArangoSearch](https://www.arangodb.com/docs/stable/arangosearch.html)
3-
See working demo at [hp4k1h5.github.io](https://hp4k1h5.github.io/#search-box).
3+
4+
See working demo at [hp4k1h5.github.io](https://hp4k1h5.github.io/#search-box).
45

56
**!Note** AQLqueryBuilder.js does NOT contain any code for the search bar, only the
67
query string parser and AQL builder. Unabstracted code for the searchbar is
7-
located
8-
[here](https://github.com/HP4k1h5/hp4k1h5.github.io/tree/main/demos/src/components/search).
8+
located [here](https://github.com/HP4k1h5/hp4k1h5.github.io/tree/main/demos/src/components/search).
9+
910
![search bar demonstration with schematic query
1011
interface](./img/searchbar_demo.png)
1112

12-
- [ overview ](#overview)
13-
- [ setup](#setup)
14-
- [ installation](#installation)
15-
- [ usage](#usage)
16-
- [ query object](#query-object)
17-
- [ boolean search logic](#boolean-search-logic)
18-
- [ default query syntax](#default-query-syntax)
19-
- [ Example](#Example)
20-
- [ bugs](#bugs)
21-
- [ contributing](#contributing)
13+
- [Patch Notes](#patch-notes)
14+
- [overview](#overview)
15+
- [setup](#setup)
16+
- [installation](#installation)
17+
- [usage](#usage)
18+
- [`buildAQL()`](#buildaql())
19+
- [boolean search logic](#boolean-search-logic)
20+
- [default query syntax](#default-query-syntax)
21+
- [Example](#example)
22+
- [bugs](#bugs)
23+
- [contributing](#contributing)
24+
25+
## Patch Notes
26+
27+
- v0.1.1
28+
- ❌ Breaking change!
29+
`buildAQL()`'s `limit` parameter no longer accepts key
30+
`end`, which has been renamed, per Arango spec, to `count`. The functionality
31+
remains the same, which is why the patch bump. Please accept my apologies.
32+
- 🔑🗝 multi-key search
33+
this can be useful if you
34+
have multiple fields with textual information. Theoretically, each
35+
chapter of a book could be stored on its own key. Or a document could
36+
have be translated into several languages, each stored on its own key.
37+
- **There are two ways to provide multiple keys to relevant functions.**
38+
1) `query.key` now accepts in addition to a string value, an array of
39+
strings over which the query is to be run. All keys listed here will be
40+
run combined with all collections provided to `query.collections`
41+
**unless** a collection has a `keys` property of its own, in which case
42+
**only** those keys are searched against.
43+
2) `query.collections[].keys` is an optional array of key names that are
44+
indexed by `query.collections[].analyzer`. **Note** It is important that
45+
any key listed in `query.collections[].keys` be indexed by the analyzer
46+
as it will impact results if such a key does not exist.
47+
48+
2249

2350
## overview
2451

@@ -87,7 +114,8 @@ also accommodate multiple key searches.
87114
___
88115
## setup
89116
90-
1) running generated AQL queries will require a running arangodb instance.
117+
1) running generated AQL queries will require a running ArangoDB v3.6+
118+
instance. This package is tested against v3.6.5 🥑
91119
92120
## installation
93121
currently there is only support for server-side use.
@@ -96,7 +124,8 @@ currently there is only support for server-side use.
96124
or `npm install --save @hp4k1h5/AQLqueryBuilder.js`
97125
in a directory containing a `package.json` file.
98126
__or__
99-
clone this repository in your node compatible project.
127+
clone this repository in your node compatible project. And run `yarn` from
128+
inside the directory.
100129
101130
2) import/require the exported functions
102131
```js
@@ -113,22 +142,15 @@ const {buildAQL} = require('@hp4k1h5/AQLqueryBuilder.js')
113142
__for up-to-date documentation, run `yarn doc && serve docs/` from the project
114143
directory root.__
115144

116-
AQLqueryBuilder aims to provide cross-collection and cross-language boolean
117-
search capabilities to the library's user. Currently, this library makes a
118-
number of assumptions about the way your data is stored and indexed, but these
119-
are hopefully compatible with a wide range of setups.
120-
121-
The primary assumption this library makes is that the data you are trying to
122-
query against is indexed by an ArangoSearch View, and that all documents index
123-
the same exact field. This field, passed to the builder as a key on the
124-
`query` object passed to e.g. `buildAQL()`, can be indexed by any number of
125-
analyzers, and the query will target all supplied collections simultaneously.
126-
This allows for true multi-language search, provided all analyzers and
127-
indexers index the same key as all other documents in the view. While there
128-
are plans to expand on this functionality to provide multi-key search, this
129-
library is primarily built for academic and textual searches, and is ideally
130-
suited for documents like books, articles, and other media where most of the
131-
data resides in a single place, i.e. document `key`, or `field`.
145+
AQLqueryBuilder aims to provide cross-collection and cross-language, multi-key
146+
boolean search capabilities to the library's user.
147+
148+
Please ensure that the data you are trying to query against is indexed by an
149+
ArangoSearch View. The query will target all combinations of provided
150+
collections, analyzers and keys an simultaneously. This allows for granular
151+
multi-language search. This library is primarily built for academic and
152+
textual searches, and is ideally suited for documents like books, articles,
153+
and other text-heavy media.
132154

133155
AQLqueryBuilder works best as a document query tool. Leveraging ArangoSearch's
134156
built-in language stemming analyzers allows for complex search phrases to be
@@ -143,55 +165,112 @@ const queryObject = {
143165
"view": "the_arango-search_view-name",
144166
"collections": [{
145167
"name": "collection_name",
146-
"analyzer": "analyzer_name"
168+
"analyzer": "analyzer_name",
169+
"keys": ["text"]
147170
}],
148171
"query": "+'query string' -for +parseQuery ?to parse"
149172
}
150173

151-
const aqlQuery = buildAQL(queryObject)
174+
const limit = {start:0, count: 20}
175+
176+
const aqlQuery = buildAQL(queryObject, limit)
152177

153178
// ... const cursor = await db.query(aqlQuery)
154-
// ... const cursor = await db.query(buildAQL(queryObject, {start:21, end:40})
179+
// ... const cursor = await db.query(buildAQL(queryObject, {start:20, count:20})
155180
```
156181

157182
Generate documenation with `yarn doc && serve docs/` or see more examples in
158183
e.g. [tests/search.ts](tests/search.ts)
159184

160-
### query object
185+
___
186+
187+
### `buildAQL()`
161188

162-
`buildAQL` accepts an object with the following properties:
189+
`buildAQL` accepts two parameters: `query` and `limit`
163190

191+
#### query
164192
**view**: *string* (required): the name of the ArangoSearch view the query
165193
will be run against
166194

167-
**collections** (required): the names of the collections indexed by @view to query
168-
169-
**terms** (required): either an array of `term` interfaces or a string to be
170-
parsed by `parseQuery`
171-
172-
**key** (optional | default: "text"): the name of the Arango document key to search
173-
within.
195+
**collections** (required): an array of objects of the indexed collections
196+
to query. Objects have the following shape:
197+
```json
198+
{
199+
"name": "collection-name",
200+
"analyzer": "analyzer_name",
201+
"keys": ["text", "summary", "notes"]
202+
}
203+
```
204+
`keys` are optional, though if key names are provided to `query.key`, and
205+
not all those keys are indexed by the collection, it is advisable to
206+
explicitly list only those keys on documents in that collection that are
207+
indexed by the analyzer. If a collection is indexed by multiple analyzers
208+
please list each collection-analyzer combination along with their relevant
209+
keys, unless a unified set of keys is provided to `query.key`.
210+
211+
**query** (required): either an array of `term` interfaces or a query string
212+
to be parsed by `parseQuery`.
213+
- **term** (optional): a JSON object representing the search.
214+
- **val** *string* (required): a search term or phrase. For PHRASE
215+
searches, include one "quoted phrase" per val. For TOKENS you may combine
216+
multiple terms under a single analyzer or use one `term` per token.
217+
- **op** *string* (required): boolean operator; **one of `+ ? -`,**
218+
representing `AND OR NOT` respectively
219+
- **type** *string* (required): **one of `"phr"`** for PHRASES **or `"tok"`**
220+
for TOKENS.
221+
222+
**key** (optional | default: "text"): the name of the Arango document key to
223+
search within.
224+
225+
**filters** (optional): a list of filter interfaces. See [arango FILTER
226+
operations](https://www.arangodb.com/docs/stable/aql/operations-filter.html)
227+
for more details. All [Arango
228+
operators](https://www.arangodb.com/docs/3.6/aql/operators.html ) Filters have
229+
the following shape:
230+
```json
231+
{
232+
"field": "the name of the field, i.e. Arango document key to filter on",
233+
"op": "one of: == != < > <= >= IN NOT IN LIKE NOT LIKE =~ !~ ",
234+
"val": "the "
235+
}
236+
```
174237

175-
**filters** (optional): a list of @filter interfaces
238+
#### limit
239+
an object with the following keys:
240+
- `start` (integer) 0-based result pagination lower bound.
241+
- `count` (integer) total number of results to return. ⚠ see CHANGELOG v0.1.1
176242

177-
___
243+
to bring back up to the first 10 results
244+
```json
245+
{"start":0, "count":10}
246+
```
247+
and the next page would be
248+
```json
249+
{"start":10, "count":10}
250+
```
178251

179-
#### `query` Example
252+
#### `query` example
180253
```json
181254
{
182255
"view": "the_arango-search_view-name",
183256
"collections": [
184257
{
185-
"name":
186-
"collection_name", "analyzer":
187-
"analyzer_name"
258+
"name":"collection_name",
259+
"analyzer": "analyzer_name",
260+
"keys": ["text", "summary", "notes"]
261+
},
262+
{
263+
"name":"collection_es",
264+
"analyzer": "analyzer_es",
265+
"keys": ["texto", "resumen", "notas"]
188266
}
189267
],
190-
"key": "text",
191-
"query": "either a +query ?\"string for parseQuery to parse\"",
268+
"query": "either a +query ?\"string for parseQuery to parse\" texto en español",
192269
"query": [
193270
{"type": "phr", "op": "?", "val": "\"!!! OR a list of query objects\""},
194-
{"type": "tok", "op": "-", "val": "tokens"}
271+
{"type": "phr", "op": "?", "val": "optional phrases"},
272+
{"type": "tok", "op": "-", "val": "excluded tokens"},
273+
{"type": "tok", "op": "+", "val": "mandatory tokens"}
195274
],
196275
"filters": [
197276
{
@@ -203,12 +282,9 @@ ___
203282
}
204283
```
205284

206-
207285
___
208286
### boolean search logic
209-
210287
Quoting [mit's Database Search Tips](https://libguides.mit.edu/c.php?g=175963&p=1158594):
211-
212288
> Boolean operators form the basis of mathematical sets and database logic.
213289
They connect your search words together to either narrow or broaden your
214290
set of results. The three basic boolean operators are: AND, OR, and NOT.
@@ -289,5 +365,4 @@ ___
289365
please see [bugs](https://github.com/HP4k1h5/AQLqueryBuilder.js/issues/new?assignees=HP4k1h5&labels=bug&template=bug_report.md&title=basic)
290366

291367
## contributing
292-
please see [./.github/CONTRIBUTING.md]
293-
368+
please see [CONTRIBUTING](./.github/CONTRIBUTING.md)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
},
3333
"devDependencies": {
3434
"@types/chai": "^4.2.11",
35-
"@types/mocha": "^7.0.2",
35+
"@types/mocha": "^8.0.0",
3636
"@types/node": "^14.0.13",
3737
"chai": "^4.2.0",
3838
"mocha": "^8.0.1",

src/index.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@ 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+
collectKeys(query)
1722

1823
const SEARCH = buildSearch(query)
1924
const FILTER = query.filters && buildFilters(query.filters)
@@ -23,7 +28,7 @@ export function buildAQL(
2328
FOR doc IN ${aql.literal(query.view)}
2429
${SEARCH}
2530
${FILTER}
26-
LIMIT ${limit.start}, ${limit.end}
31+
LIMIT ${limit.start}, ${limit.count}
2732
RETURN doc`
2833
}
2934

@@ -33,3 +38,18 @@ function validateQuery(query: query) {
3338
if (!query.collections.length)
3439
throw new Error('query.collections must have at least one name')
3540
}
41+
42+
function collectKeys(query: query) {
43+
/* unify query.key */
44+
let _keys: string[]
45+
if (typeof query.key == 'string') {
46+
_keys = [query.key]
47+
} else if (!query.key) {
48+
_keys = ['text']
49+
} else _keys = query.key
50+
51+
query.collections = query.collections.map((c) => {
52+
if (!c.keys) c.keys = _keys
53+
return c
54+
})
55+
}

0 commit comments

Comments
 (0)