Skip to content

Commit 5dd48e2

Browse files
release(2.1.0): search_after support added (#134)
* task(Readme): readme file update * fix(eslint): all lint issues, remove unused files * fix(eslint): constructor, prototype issues for ESConnector class * fix(lint): lint issues fixed in all.js and buildOrder.js * feature(Client): all client APIs updated * fix(replaceOrCreate): response fix * task(README, examples): update README and example server * Release: Version 2.0.0 * task(filter): added _source filter support for fields * release(2.0.1): version update * task(search_after): add elasticsearch search after support in filter * task(search-after): updated readme * release(2.1.0): search_after support added
1 parent e8140ca commit 5dd48e2

File tree

13 files changed

+107
-9
lines changed

13 files changed

+107
-9
lines changed

README.md

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ Elasticsearch(versions 6.x and 7.x) datasource connector for [Loopback 3.x](http
1414
- [Recommended properties](#recommended)
1515
- [Optional properties](#optional)
1616
- [Sample for copy paste](#sample)
17-
- [How to achieve Instant search](#how-to-achieve-instant-search)
17+
18+
- [Elasticsearch SearchAfter Support](#elasticsearch-searchafter-support)
19+
- [Example](#about-the-example-app)
1820
- [Troubleshooting](#troubleshooting)
1921
- [Contributing](#contributing)
2022
- [Frequently Asked Questions](#faqs)
@@ -154,6 +156,60 @@ npm install loopback-connector-esv6 --save --save-exact
154156

155157
2.You can peek at `/examples/server/datasources.json` for more hints.
156158

159+
## Elasticsearch SearchAfter Support
160+
161+
- ```search_after``` feature of elasticsearch is supported in loopback filter.
162+
- For this, you need to create a property in model called ```_search_after``` with loopback type ```["any"]```. This field cannot be updated using in connector.
163+
- Elasticsearch ```sort``` value will return in this field.
164+
- You need pass ```_search_after``` value in ```searchafter``` key of loopback filter.
165+
- Example filter query for ```find```.
166+
167+
```json
168+
{
169+
"where": {
170+
"username": "hello"
171+
},
172+
"order": "created DESC",
173+
"searchafter": [
174+
1580902552905
175+
],
176+
"limit": 4
177+
}
178+
```
179+
180+
- Example result.
181+
182+
```json
183+
[
184+
{
185+
"id": "1bb2dd63-c7b9-588e-a942-15ca4f891a80",
186+
"username": "test",
187+
"_search_after": [
188+
1580902552905
189+
],
190+
"created": "2020-02-05T11:35:52.905Z"
191+
},
192+
{
193+
"id": "fd5ea4df-f159-5816-9104-22147f2a740f",
194+
"username": "test3",
195+
"_search_after": [
196+
1580902552901
197+
],
198+
"created": "2020-02-05T11:35:52.901Z"
199+
},
200+
{
201+
"id": "047c0adb-13ea-5f80-a772-3d2a4691d47a",
202+
"username": "test4",
203+
"_search_after": [
204+
1580902552897
205+
],
206+
"created": "2020-02-05T11:35:52.897Z"
207+
}
208+
]
209+
```
210+
211+
- This is useful for pagination. To go to previous page, change sorting order.
212+
157213
## About the example app
158214

159215
1. The `examples` directory contains a loopback app which uses this connector.

examples/common/models/user-model.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
"name": "UserModel",
33
"base": "User",
44
"idInjection": true,
5-
"properties": {},
5+
"properties": {
6+
"_search_after": {
7+
"type": ["any"]
8+
}
9+
},
610
"validations": [],
711
"relations": {
812
"globalConfigModels": {

lib/buildFilter.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ function buildFilter(modelName, idName, criteria = {}, size = null, offset = nul
4444
};
4545
}
4646
}
47+
if (criteria.searchafter && Array.isArray(criteria.searchafter)
48+
&& criteria.searchafter.length) {
49+
filter.body.search_after = criteria.searchafter;
50+
filter.from = undefined;
51+
}
4752
if (criteria.order) {
4853
log('ESConnector.prototype.buildFilter', 'will delegate sorting to buildOrder()');
4954
filter.body.sort = self.buildOrder(modelName, idName, criteria.order);

lib/create.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const _ = require('lodash');
22
const log = require('debug')('loopback:connector:elasticsearch');
3+
// CONSTANTS
4+
const SEARCHAFTERKEY = '_search_after';
35

46
function create(model, data, done) {
57
const self = this;
@@ -26,6 +28,9 @@ function create(model, data, done) {
2628
method = 'index'; // if there is no/empty id field, we must use the index method to create it (API 5.0)
2729
}
2830
document.body.docType = model;
31+
if (document.body[SEARCHAFTERKEY]) {
32+
document.body[SEARCHAFTERKEY] = undefined;
33+
}
2934
self.db[method](
3035
document
3136
).then(

lib/esConnector.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ const { updateAll } = require('./updateAll');
3535
const { updateAttributes } = require('./updateAttributes');
3636
const { updateOrCreate } = require('./updateOrCreate');
3737

38+
// CONSTANTS
39+
const SEARCHAFTERKEY = '_search_after';
40+
3841
/**
3942
* Connector constructor
4043
* @param {object} datasource settings
@@ -212,7 +215,7 @@ ESConnector.prototype.getValueFromProperty = function (property, value) {
212215
* @param {Object} data from DB
213216
* @returns {object} modeled document
214217
*/
215-
ESConnector.prototype.matchDataToModel = function (modelName, data, esId, idName) {
218+
ESConnector.prototype.matchDataToModel = function (modelName, data, esId, idName, sort) {
216219
/*
217220
log('ESConnector.prototype.matchDataToModel', 'modelName',
218221
modelName, 'data', JSON.stringify(data,null,0));
@@ -239,6 +242,7 @@ ESConnector.prototype.matchDataToModel = function (modelName, data, esId, idName
239242
);
240243
}
241244
});
245+
document[SEARCHAFTERKEY] = sort;
242246
log('ESConnector.prototype.matchDataToModel', 'document', JSON.stringify(document, null, 0));
243247
return document;
244248
} catch (err) {
@@ -258,7 +262,7 @@ ESConnector.prototype.dataSourceToModel = function (modelName, data, idName) {
258262

259263
// return data._source; // TODO: super-simplify?
260264
// eslint-disable-next-line no-underscore-dangle
261-
return this.matchDataToModel(modelName, data._source, data._id, idName);
265+
return this.matchDataToModel(modelName, data._source, data._id, idName, data.sort || []);
262266
};
263267

264268
/**

lib/find.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ function find(modelName, id, done) {
88
if (id === undefined || id === null) {
99
throw new Error('id not set!');
1010
}
11-
11+
const idName = self.idName(modelName);
1212
const defaults = self.addDefaults(modelName, 'find');
1313
self.db.get(_.defaults({
1414
id: self.getDocumentId(id)
1515
}, defaults)).then(({ body }) => {
16-
done(null, self.dataSourceToModel(modelName, body));
16+
done(null, self.dataSourceToModel(modelName, body, idName));
1717
}).catch((error) => {
1818
log('ESConnector.prototype.find', error.message);
1919
done(error);

lib/replaceById.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const _ = require('lodash');
22
const log = require('debug')('loopback:connector:elasticsearch');
3+
// CONSTANTS
4+
const SEARCHAFTERKEY = '_search_after';
35

46
function replaceById(modelName, id, data, options, callback) {
57
const self = this;
@@ -21,6 +23,9 @@ function replaceById(modelName, id, data, options, callback) {
2123
if (Object.prototype.hasOwnProperty.call(modelProperties, idName)) {
2224
document.body[idName] = id;
2325
}
26+
if (document.body[SEARCHAFTERKEY]) {
27+
document.body[SEARCHAFTERKEY] = undefined;
28+
}
2429
log('ESConnector.prototype.replaceById', 'document', document);
2530
self.db.index(
2631
document

lib/replaceOrCreate.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const _ = require('lodash');
22
const log = require('debug')('loopback:connector:elasticsearch');
3+
// CONSTANTS
4+
const SEARCHAFTERKEY = '_search_after';
35

46
function replaceOrCreate(modelName, data, callback) {
57
const self = this;
@@ -16,6 +18,9 @@ function replaceOrCreate(modelName, data, callback) {
1618
document.body = {};
1719
_.assign(document.body, data);
1820
document.body.docType = modelName;
21+
if (document.body[SEARCHAFTERKEY]) {
22+
document.body[SEARCHAFTERKEY] = undefined;
23+
}
1924
log('ESConnector.prototype.replaceOrCreate', 'document', document);
2025
self.db.index(
2126
document

lib/save.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const _ = require('lodash');
22
const log = require('debug')('loopback:connector:elasticsearch');
3+
// CONSTANTS
4+
const SEARCHAFTERKEY = '_search_after';
35

46
// eslint-disable-next-line consistent-return
57
function save(model, data, done) {
@@ -16,6 +18,9 @@ function save(model, data, done) {
1618
return done('Document id not setted!', null);
1719
}
1820
data.docType = model;
21+
if (data[SEARCHAFTERKEY]) {
22+
data[SEARCHAFTERKEY] = undefined;
23+
}
1924
self.db.update(_.defaults({
2025
id,
2126
body: {

lib/updateAll.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const _ = require('lodash');
22
const log = require('debug')('loopback:connector:elasticsearch');
3+
// CONSTANTS
4+
const SEARCHAFTERKEY = '_search_after';
35

46
function updateAll(model, where, data, options, cb) {
57
const self = this;
@@ -20,7 +22,7 @@ function updateAll(model, where, data, options, cb) {
2022
params: {}
2123
};
2224
_.forEach(data, (value, key) => {
23-
if (key !== '_id' || key !== idName) {
25+
if (key !== '_id' && key !== idName && key !== SEARCHAFTERKEY) {
2426
// default language for inline scripts is painless if ES 5, so this needs the extra params.
2527
reqBody.script.inline += `ctx._source.${key}=params.${key};`;
2628
reqBody.script.params[key] = value;

lib/updateAttributes.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const _ = require('lodash');
22
const log = require('debug')('loopback:connector:elasticsearch');
3+
// CONSTANTS
4+
const SEARCHAFTERKEY = '_search_after';
35

46
function updateAttributes(modelName, id, data, callback) {
57
const self = this;
@@ -22,7 +24,7 @@ function updateAttributes(modelName, id, data, callback) {
2224
params: {}
2325
};
2426
_.forEach(data, (value, key) => {
25-
if (key !== '_id' || key !== idName) {
27+
if (key !== '_id' && key !== idName && key !== SEARCHAFTERKEY) {
2628
// default language for inline scripts is painless if ES 5, so this needs the extra params.
2729
reqBody.script.inline += `ctx._source.${key}=params.${key};`;
2830
reqBody.script.params[key] = value;

lib/updateOrCreate.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const _ = require('lodash');
22
const log = require('debug')('loopback:connector:elasticsearch');
3+
// CONSTANTS
4+
const SEARCHAFTERKEY = '_search_after';
35

46
function updateOrCreate(modelName, data, callback) {
57
const self = this;
@@ -13,6 +15,9 @@ function updateOrCreate(modelName, data, callback) {
1315

1416
const defaults = self.addDefaults(modelName, 'updateOrCreate');
1517
data.docType = modelName;
18+
if (data[SEARCHAFTERKEY]) {
19+
data[SEARCHAFTERKEY] = undefined;
20+
}
1621
self.db.update(_.defaults({
1722
id,
1823
body: {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "loopback-connector-esv6",
3-
"version": "2.0.1",
3+
"version": "2.1.0",
44
"description": "LoopBack Connector for Elasticsearch 6.x and 7.x",
55
"main": "index.js",
66
"scripts": {

0 commit comments

Comments
 (0)