@elastic/elasticsearch 7.7.0-rc.2 → 7.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/api/api/async_search.submit.js +4 -0
- package/api/api/autoscaling.delete_autoscaling_policy.js +78 -0
- package/api/api/autoscaling.get_autoscaling_policy.js +78 -0
- package/api/api/autoscaling.put_autoscaling_policy.js +82 -0
- package/api/api/bulk.js +4 -0
- package/api/api/cluster.delete_component_template.js +1 -1
- package/api/api/cluster.delete_voting_config_exclusions.js +79 -0
- package/api/api/cluster.exists_component_template.js +86 -0
- package/api/api/cluster.get_component_template.js +1 -1
- package/api/api/cluster.post_voting_config_exclusions.js +82 -0
- package/api/api/cluster.put_component_template.js +1 -1
- package/api/api/delete_by_query.js +4 -0
- package/api/api/eql.search.js +1 -1
- package/api/api/exists.js +4 -0
- package/api/api/exists_source.js +4 -0
- package/api/api/explain.js +4 -0
- package/api/api/get.js +4 -0
- package/api/api/get_source.js +4 -0
- package/api/api/indices.delete_index_template.js +86 -0
- package/api/api/indices.exists_index_template.js +88 -0
- package/api/api/indices.get_index_template.js +87 -0
- package/api/api/indices.put_index_template.js +91 -0
- package/api/api/indices.simulate_index_template.js +87 -0
- package/api/api/mget.js +4 -0
- package/api/api/ml.delete_data_frame_analytics.js +2 -1
- package/api/api/ml.validate.js +1 -0
- package/api/api/ml.validate_detector.js +1 -0
- package/api/api/search.js +4 -0
- package/api/api/searchable_snapshots.clear_cache.js +83 -0
- package/api/api/searchable_snapshots.mount.js +94 -0
- package/api/api/searchable_snapshots.repository_stats.js +78 -0
- package/api/api/searchable_snapshots.stats.js +77 -0
- package/api/api/snapshot.cleanup_repository.js +1 -1
- package/api/api/tasks.cancel.js +2 -0
- package/api/api/update.js +4 -0
- package/api/api/update_by_query.js +4 -0
- package/api/index.js +39 -1
- package/api/requestParams.d.ts +119 -3
- package/index.d.ts +156 -16
- package/index.js +9 -2
- package/lib/Connection.js +8 -2
- package/lib/Helpers.d.ts +22 -5
- package/lib/Helpers.js +328 -32
- package/lib/Transport.js +30 -15
- package/lib/pool/BaseConnectionPool.js +2 -0
- package/lib/pool/CloudConnectionPool.js +1 -1
- package/lib/pool/ConnectionPool.js +12 -2
- package/package.json +21 -13
package/lib/Helpers.js
CHANGED
|
@@ -4,13 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
'use strict'
|
|
6
6
|
|
|
7
|
+
/* eslint camelcase: 0 */
|
|
8
|
+
|
|
9
|
+
const { Readable } = require('stream')
|
|
7
10
|
const { promisify } = require('util')
|
|
8
11
|
const { ResponseError, ConfigurationError } = require('./errors')
|
|
9
12
|
|
|
10
13
|
const pImmediate = promisify(setImmediate)
|
|
11
14
|
const sleep = promisify(setTimeout)
|
|
12
|
-
const kGetHits = Symbol('elasticsearch-get-hits')
|
|
13
15
|
const kClient = Symbol('elasticsearch-client')
|
|
16
|
+
/* istanbul ignore next */
|
|
14
17
|
const noop = () => {}
|
|
15
18
|
|
|
16
19
|
class Helpers {
|
|
@@ -19,23 +22,22 @@ class Helpers {
|
|
|
19
22
|
this.maxRetries = opts.maxRetries
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
[kGetHits] (body) {
|
|
23
|
-
if (body.hits && body.hits.hits) {
|
|
24
|
-
return body.hits.hits.map(d => d._source)
|
|
25
|
-
}
|
|
26
|
-
return []
|
|
27
|
-
}
|
|
28
|
-
|
|
29
25
|
/**
|
|
30
26
|
* Runs a search operation. The only difference between client.search and this utility,
|
|
31
27
|
* is that we are only returning the hits to the user and not the full ES response.
|
|
28
|
+
* This helper automatically adds `filter_path=hits.hits._source` to the querystring,
|
|
29
|
+
* as it will only need the documents source.
|
|
32
30
|
* @param {object} params - The Elasticsearch's search parameters.
|
|
33
31
|
* @param {object} options - The client optional configuration for this request.
|
|
34
32
|
* @return {array} The documents that matched the request.
|
|
35
33
|
*/
|
|
36
34
|
async search (params, options) {
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
appendFilterPath('hits.hits._source', params, true)
|
|
36
|
+
const { body } = await this[kClient].search(params, options)
|
|
37
|
+
if (body.hits && body.hits.hits) {
|
|
38
|
+
return body.hits.hits.map(d => d._source)
|
|
39
|
+
}
|
|
40
|
+
return []
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -63,6 +65,8 @@ class Helpers {
|
|
|
63
65
|
options.ignore = [429]
|
|
64
66
|
}
|
|
65
67
|
params.scroll = params.scroll || '1m'
|
|
68
|
+
appendFilterPath('_scroll_id', params, false)
|
|
69
|
+
const { method, body, index, ...querystring } = params
|
|
66
70
|
|
|
67
71
|
let response = null
|
|
68
72
|
for (let i = 0; i < maxRetries; i++) {
|
|
@@ -74,33 +78,31 @@ class Helpers {
|
|
|
74
78
|
throw new ResponseError(response)
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
let
|
|
81
|
+
let scroll_id = response.body._scroll_id
|
|
78
82
|
let stop = false
|
|
79
83
|
const clear = async () => {
|
|
80
84
|
stop = true
|
|
81
85
|
await this[kClient].clearScroll(
|
|
82
|
-
{ body: { scroll_id
|
|
86
|
+
{ body: { scroll_id } },
|
|
83
87
|
{ ignore: [400] }
|
|
84
88
|
)
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
while (response.body.hits.hits.length > 0) {
|
|
88
|
-
|
|
91
|
+
while (response.body.hits && response.body.hits.hits.length > 0) {
|
|
92
|
+
scroll_id = response.body._scroll_id
|
|
89
93
|
response.clear = clear
|
|
90
|
-
|
|
94
|
+
addDocumentsGetter(response)
|
|
91
95
|
|
|
92
96
|
yield response
|
|
93
97
|
|
|
94
|
-
if (!
|
|
98
|
+
if (!scroll_id || stop === true) {
|
|
95
99
|
break
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
for (let i = 0; i < maxRetries; i++) {
|
|
99
103
|
response = await this[kClient].scroll({
|
|
100
|
-
|
|
101
|
-
body: {
|
|
102
|
-
scroll_id: scrollId
|
|
103
|
-
}
|
|
104
|
+
...querystring,
|
|
105
|
+
body: { scroll_id }
|
|
104
106
|
}, options)
|
|
105
107
|
if (response.statusCode !== 429) break
|
|
106
108
|
await sleep(wait)
|
|
@@ -120,11 +122,14 @@ class Helpers {
|
|
|
120
122
|
* }
|
|
121
123
|
* ```
|
|
122
124
|
* Each document is what you will find by running a scrollSearch and iterating on the hits array.
|
|
125
|
+
* This helper automatically adds `filter_path=hits.hits._source` to the querystring,
|
|
126
|
+
* as it will only need the documents source.
|
|
123
127
|
* @param {object} params - The Elasticsearch's search parameters.
|
|
124
128
|
* @param {object} options - The client optional configuration for this request.
|
|
125
129
|
* @return {iterator} the async iterator
|
|
126
130
|
*/
|
|
127
131
|
async * scrollDocuments (params, options) {
|
|
132
|
+
appendFilterPath('hits.hits._source', params, true)
|
|
128
133
|
for await (const { documents } of this.scrollSearch(params)) {
|
|
129
134
|
for (const document of documents) {
|
|
130
135
|
yield document
|
|
@@ -132,21 +137,266 @@ class Helpers {
|
|
|
132
137
|
}
|
|
133
138
|
}
|
|
134
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Creates a msearch helper instance. Once you configure it, you can use the provided
|
|
142
|
+
* `search` method to add new searches in the queue.
|
|
143
|
+
* @param {object} options - The configuration of the msearch operations.
|
|
144
|
+
* @return {object} The possible operations to run.
|
|
145
|
+
*/
|
|
146
|
+
msearch (options = {}) {
|
|
147
|
+
const client = this[kClient]
|
|
148
|
+
const {
|
|
149
|
+
operations = 5,
|
|
150
|
+
concurrency = 5,
|
|
151
|
+
flushInterval = 500,
|
|
152
|
+
retries = this.maxRetries,
|
|
153
|
+
wait = 5000,
|
|
154
|
+
...msearchOptions
|
|
155
|
+
} = options
|
|
156
|
+
|
|
157
|
+
let stopReading = false
|
|
158
|
+
let stopError = null
|
|
159
|
+
let timeoutRef = null
|
|
160
|
+
const operationsStream = new Readable({
|
|
161
|
+
objectMode: true,
|
|
162
|
+
read (size) {}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
const p = iterate()
|
|
166
|
+
const helper = {
|
|
167
|
+
then (onFulfilled, onRejected) {
|
|
168
|
+
return p.then(onFulfilled, onRejected)
|
|
169
|
+
},
|
|
170
|
+
catch (onRejected) {
|
|
171
|
+
return p.catch(onRejected)
|
|
172
|
+
},
|
|
173
|
+
stop (error = null) {
|
|
174
|
+
if (stopReading === true) return
|
|
175
|
+
stopReading = true
|
|
176
|
+
stopError = error
|
|
177
|
+
operationsStream.push(null)
|
|
178
|
+
},
|
|
179
|
+
// TODO: support abort a single search?
|
|
180
|
+
// NOTE: the validation checks are synchronous and the callback/promise will
|
|
181
|
+
// be resolved in the same tick. We might want to fix this in the future.
|
|
182
|
+
search (header, body, callback) {
|
|
183
|
+
if (stopReading === true) {
|
|
184
|
+
const error = stopError === null
|
|
185
|
+
? new ConfigurationError('The msearch processor has been stopped')
|
|
186
|
+
: stopError
|
|
187
|
+
return callback ? callback(error, {}) : Promise.reject(error)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!(typeof header === 'object' && header !== null && !Array.isArray(header))) {
|
|
191
|
+
const error = new ConfigurationError('The header should be an object')
|
|
192
|
+
return callback ? callback(error, {}) : Promise.reject(error)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!(typeof body === 'object' && body !== null && !Array.isArray(body))) {
|
|
196
|
+
const error = new ConfigurationError('The body should be an object')
|
|
197
|
+
return callback ? callback(error, {}) : Promise.reject(error)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let promise = null
|
|
201
|
+
if (callback === undefined) {
|
|
202
|
+
let onFulfilled = null
|
|
203
|
+
let onRejected = null
|
|
204
|
+
promise = new Promise((resolve, reject) => {
|
|
205
|
+
onFulfilled = resolve
|
|
206
|
+
onRejected = reject
|
|
207
|
+
})
|
|
208
|
+
callback = function callback (err, result) {
|
|
209
|
+
err ? onRejected(err) : onFulfilled(result)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
operationsStream.push([header, body, callback])
|
|
214
|
+
|
|
215
|
+
if (promise !== null) {
|
|
216
|
+
return promise
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return helper
|
|
222
|
+
|
|
223
|
+
async function iterate () {
|
|
224
|
+
const { semaphore, finish } = buildSemaphore()
|
|
225
|
+
const msearchBody = []
|
|
226
|
+
const callbacks = []
|
|
227
|
+
let loadedOperations = 0
|
|
228
|
+
timeoutRef = setTimeout(onFlushTimeout, flushInterval)
|
|
229
|
+
|
|
230
|
+
for await (const operation of operationsStream) {
|
|
231
|
+
timeoutRef.refresh()
|
|
232
|
+
loadedOperations += 1
|
|
233
|
+
msearchBody.push(operation[0], operation[1])
|
|
234
|
+
callbacks.push(operation[2])
|
|
235
|
+
if (loadedOperations >= operations) {
|
|
236
|
+
const send = await semaphore()
|
|
237
|
+
send(msearchBody.slice(), callbacks.slice())
|
|
238
|
+
msearchBody.length = 0
|
|
239
|
+
callbacks.length = 0
|
|
240
|
+
loadedOperations = 0
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
clearTimeout(timeoutRef)
|
|
245
|
+
// In some cases the previos http call does not have finished,
|
|
246
|
+
// or we didn't reach the flush bytes threshold, so we force one last operation.
|
|
247
|
+
if (loadedOperations > 0) {
|
|
248
|
+
const send = await semaphore()
|
|
249
|
+
send(msearchBody, callbacks)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await finish()
|
|
253
|
+
|
|
254
|
+
if (stopError !== null) {
|
|
255
|
+
throw stopError
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function onFlushTimeout () {
|
|
259
|
+
if (loadedOperations === 0) return
|
|
260
|
+
const msearchBodyCopy = msearchBody.slice()
|
|
261
|
+
const callbacksCopy = callbacks.slice()
|
|
262
|
+
msearchBody.length = 0
|
|
263
|
+
callbacks.length = 0
|
|
264
|
+
loadedOperations = 0
|
|
265
|
+
try {
|
|
266
|
+
const send = await semaphore()
|
|
267
|
+
send(msearchBodyCopy, callbacksCopy)
|
|
268
|
+
} catch (err) {
|
|
269
|
+
/* istanbul ignore next */
|
|
270
|
+
helper.stop(err)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// This function builds a semaphore using the concurrency
|
|
276
|
+
// options of the msearch helper. It is used inside the iterator
|
|
277
|
+
// to guarantee that no more than the number of operations
|
|
278
|
+
// allowed to run at the same time are executed.
|
|
279
|
+
// It returns a semaphore function which resolves in the next tick
|
|
280
|
+
// if we didn't reach the maximim concurrency yet, otherwise it returns
|
|
281
|
+
// a promise that resolves as soon as one of the running request has finshed.
|
|
282
|
+
// The semaphore function resolves a send function, which will be used
|
|
283
|
+
// to send the actual msearch request.
|
|
284
|
+
// It also returns a finish function, which returns a promise that is resolved
|
|
285
|
+
// when there are no longer request running.
|
|
286
|
+
function buildSemaphore () {
|
|
287
|
+
let resolveSemaphore = null
|
|
288
|
+
let resolveFinish = null
|
|
289
|
+
let running = 0
|
|
290
|
+
|
|
291
|
+
return { semaphore, finish }
|
|
292
|
+
|
|
293
|
+
function finish () {
|
|
294
|
+
return new Promise((resolve, reject) => {
|
|
295
|
+
if (running === 0) {
|
|
296
|
+
resolve()
|
|
297
|
+
} else {
|
|
298
|
+
resolveFinish = resolve
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function semaphore () {
|
|
304
|
+
if (running < concurrency) {
|
|
305
|
+
running += 1
|
|
306
|
+
return pImmediate(send)
|
|
307
|
+
} else {
|
|
308
|
+
return new Promise((resolve, reject) => {
|
|
309
|
+
resolveSemaphore = resolve
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function send (msearchBody, callbacks) {
|
|
315
|
+
/* istanbul ignore if */
|
|
316
|
+
if (running > concurrency) {
|
|
317
|
+
throw new Error('Max concurrency reached')
|
|
318
|
+
}
|
|
319
|
+
msearchOperation(msearchBody, callbacks, () => {
|
|
320
|
+
running -= 1
|
|
321
|
+
if (resolveSemaphore) {
|
|
322
|
+
running += 1
|
|
323
|
+
resolveSemaphore(send)
|
|
324
|
+
resolveSemaphore = null
|
|
325
|
+
} else if (resolveFinish && running === 0) {
|
|
326
|
+
resolveFinish()
|
|
327
|
+
}
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function msearchOperation (msearchBody, callbacks, done) {
|
|
333
|
+
let retryCount = retries
|
|
334
|
+
|
|
335
|
+
// Instead of going full on async-await, which would make the code easier to read,
|
|
336
|
+
// we have decided to use callback style instead.
|
|
337
|
+
// This because every time we use async await, V8 will create multiple promises
|
|
338
|
+
// behind the scenes, making the code slightly slower.
|
|
339
|
+
tryMsearch(msearchBody, callbacks, retrySearch)
|
|
340
|
+
function retrySearch (msearchBody, callbacks) {
|
|
341
|
+
if (msearchBody.length > 0 && retryCount > 0) {
|
|
342
|
+
retryCount -= 1
|
|
343
|
+
setTimeout(tryMsearch, wait, msearchBody, callbacks, retrySearch)
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
done()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// This function never returns an error, if the msearch operation fails,
|
|
351
|
+
// the error is dispatched to all search executors.
|
|
352
|
+
function tryMsearch (msearchBody, callbacks, done) {
|
|
353
|
+
client.msearch(Object.assign({}, msearchOptions, { body: msearchBody }), (err, results) => {
|
|
354
|
+
const retryBody = []
|
|
355
|
+
const retryCallbacks = []
|
|
356
|
+
if (err) {
|
|
357
|
+
addDocumentsGetter(results)
|
|
358
|
+
for (const callback of callbacks) {
|
|
359
|
+
callback(err, results)
|
|
360
|
+
}
|
|
361
|
+
return done(retryBody, retryCallbacks)
|
|
362
|
+
}
|
|
363
|
+
const { responses } = results.body
|
|
364
|
+
for (let i = 0, len = responses.length; i < len; i++) {
|
|
365
|
+
const response = responses[i]
|
|
366
|
+
if (response.status === 429 && retryCount > 0) {
|
|
367
|
+
retryBody.push(msearchBody[i * 2])
|
|
368
|
+
retryBody.push(msearchBody[(i * 2) + 1])
|
|
369
|
+
retryCallbacks.push(callbacks[i])
|
|
370
|
+
continue
|
|
371
|
+
}
|
|
372
|
+
const result = { ...results, body: response }
|
|
373
|
+
addDocumentsGetter(result)
|
|
374
|
+
if (response.status >= 400) {
|
|
375
|
+
callbacks[i](new ResponseError(result), result)
|
|
376
|
+
} else {
|
|
377
|
+
callbacks[i](null, result)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
done(retryBody, retryCallbacks)
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
135
386
|
/**
|
|
136
387
|
* Creates a bulk helper instance. Once you configure it, you can pick which operation
|
|
137
388
|
* to execute with the given dataset, index, create, update, and delete.
|
|
138
389
|
* @param {object} options - The configuration of the bulk operation.
|
|
139
|
-
* @return {object} The possible
|
|
390
|
+
* @return {object} The possible operations to run with the datasource.
|
|
140
391
|
*/
|
|
141
392
|
bulk (options) {
|
|
142
|
-
// TODO: add an interval to force flush the body
|
|
143
|
-
// to handle the slow producer problem
|
|
144
393
|
const client = this[kClient]
|
|
145
394
|
const { serialize, deserialize } = client.serializer
|
|
146
395
|
const {
|
|
147
396
|
datasource,
|
|
148
397
|
onDocument,
|
|
149
398
|
flushBytes = 5000000,
|
|
399
|
+
flushInterval = 30000,
|
|
150
400
|
concurrency = 5,
|
|
151
401
|
retries = this.maxRetries,
|
|
152
402
|
wait = 5000,
|
|
@@ -166,6 +416,7 @@ class Helpers {
|
|
|
166
416
|
}
|
|
167
417
|
|
|
168
418
|
let shouldAbort = false
|
|
419
|
+
let timeoutRef = null
|
|
169
420
|
const stats = {
|
|
170
421
|
total: 0,
|
|
171
422
|
failed: 0,
|
|
@@ -177,8 +428,7 @@ class Helpers {
|
|
|
177
428
|
}
|
|
178
429
|
|
|
179
430
|
const p = iterate()
|
|
180
|
-
|
|
181
|
-
return {
|
|
431
|
+
const helper = {
|
|
182
432
|
then (onFulfilled, onRejected) {
|
|
183
433
|
return p.then(onFulfilled, onRejected)
|
|
184
434
|
},
|
|
@@ -186,12 +436,15 @@ class Helpers {
|
|
|
186
436
|
return p.catch(onRejected)
|
|
187
437
|
},
|
|
188
438
|
abort () {
|
|
439
|
+
clearTimeout(timeoutRef)
|
|
189
440
|
shouldAbort = true
|
|
190
441
|
stats.aborted = true
|
|
191
442
|
return this
|
|
192
443
|
}
|
|
193
444
|
}
|
|
194
445
|
|
|
446
|
+
return helper
|
|
447
|
+
|
|
195
448
|
/**
|
|
196
449
|
* Function that iterates over the given datasource and start a bulk operation as soon
|
|
197
450
|
* as it reaches the configured bulk size. It's designed to use the Node.js asynchronous
|
|
@@ -208,9 +461,11 @@ class Helpers {
|
|
|
208
461
|
let actionBody = ''
|
|
209
462
|
let payloadBody = ''
|
|
210
463
|
let chunkBytes = 0
|
|
464
|
+
timeoutRef = setTimeout(onFlushTimeout, flushInterval)
|
|
211
465
|
|
|
212
466
|
for await (const chunk of datasource) {
|
|
213
467
|
if (shouldAbort === true) break
|
|
468
|
+
timeoutRef.refresh()
|
|
214
469
|
const action = onDocument(chunk)
|
|
215
470
|
const operation = Array.isArray(action)
|
|
216
471
|
? Object.keys(action[0])[0]
|
|
@@ -219,21 +474,20 @@ class Helpers {
|
|
|
219
474
|
actionBody = serialize(action)
|
|
220
475
|
payloadBody = typeof chunk === 'string' ? chunk : serialize(chunk)
|
|
221
476
|
chunkBytes += Buffer.byteLength(actionBody) + Buffer.byteLength(payloadBody)
|
|
222
|
-
bulkBody.push(actionBody)
|
|
223
|
-
bulkBody.push(payloadBody)
|
|
477
|
+
bulkBody.push(actionBody, payloadBody)
|
|
224
478
|
} else if (operation === 'update') {
|
|
225
479
|
actionBody = serialize(action[0])
|
|
226
480
|
payloadBody = typeof chunk === 'string'
|
|
227
|
-
? `{doc:${chunk}}`
|
|
481
|
+
? `{"doc":${chunk}}`
|
|
228
482
|
: serialize({ doc: chunk, ...action[1] })
|
|
229
483
|
chunkBytes += Buffer.byteLength(actionBody) + Buffer.byteLength(payloadBody)
|
|
230
|
-
bulkBody.push(actionBody)
|
|
231
|
-
bulkBody.push(payloadBody)
|
|
484
|
+
bulkBody.push(actionBody, payloadBody)
|
|
232
485
|
} else if (operation === 'delete') {
|
|
233
486
|
actionBody = serialize(action)
|
|
234
487
|
chunkBytes += Buffer.byteLength(actionBody)
|
|
235
488
|
bulkBody.push(actionBody)
|
|
236
489
|
} else {
|
|
490
|
+
clearTimeout(timeoutRef)
|
|
237
491
|
throw new ConfigurationError(`Bulk helper invalid action: '${operation}'`)
|
|
238
492
|
}
|
|
239
493
|
|
|
@@ -246,6 +500,7 @@ class Helpers {
|
|
|
246
500
|
}
|
|
247
501
|
}
|
|
248
502
|
|
|
503
|
+
clearTimeout(timeoutRef)
|
|
249
504
|
// In some cases the previos http call does not have finished,
|
|
250
505
|
// or we didn't reach the flush bytes threshold, so we force one last operation.
|
|
251
506
|
if (shouldAbort === false && chunkBytes > 0) {
|
|
@@ -268,6 +523,21 @@ class Helpers {
|
|
|
268
523
|
stats.total = stats.successful + stats.failed
|
|
269
524
|
|
|
270
525
|
return stats
|
|
526
|
+
|
|
527
|
+
async function onFlushTimeout () {
|
|
528
|
+
if (chunkBytes === 0) return
|
|
529
|
+
stats.bytes += chunkBytes
|
|
530
|
+
const bulkBodyCopy = bulkBody.slice()
|
|
531
|
+
bulkBody.length = 0
|
|
532
|
+
chunkBytes = 0
|
|
533
|
+
try {
|
|
534
|
+
const send = await semaphore()
|
|
535
|
+
send(bulkBodyCopy)
|
|
536
|
+
} catch (err) {
|
|
537
|
+
/* istanbul ignore next */
|
|
538
|
+
helper.abort()
|
|
539
|
+
}
|
|
540
|
+
}
|
|
271
541
|
}
|
|
272
542
|
|
|
273
543
|
// This function builds a semaphore using the concurrency
|
|
@@ -308,6 +578,7 @@ class Helpers {
|
|
|
308
578
|
|
|
309
579
|
function semaphore () {
|
|
310
580
|
if (running < concurrency) {
|
|
581
|
+
running += 1
|
|
311
582
|
return pImmediate(send)
|
|
312
583
|
} else {
|
|
313
584
|
return new Promise((resolve, reject) => {
|
|
@@ -318,10 +589,9 @@ class Helpers {
|
|
|
318
589
|
|
|
319
590
|
function send (bulkBody) {
|
|
320
591
|
/* istanbul ignore if */
|
|
321
|
-
if (running
|
|
592
|
+
if (running > concurrency) {
|
|
322
593
|
throw new Error('Max concurrency reached')
|
|
323
594
|
}
|
|
324
|
-
running += 1
|
|
325
595
|
bulkOperation(bulkBody, err => {
|
|
326
596
|
running -= 1
|
|
327
597
|
if (err) {
|
|
@@ -329,6 +599,7 @@ class Helpers {
|
|
|
329
599
|
error = err
|
|
330
600
|
}
|
|
331
601
|
if (resolveSemaphore) {
|
|
602
|
+
running += 1
|
|
332
603
|
resolveSemaphore(send)
|
|
333
604
|
resolveSemaphore = null
|
|
334
605
|
} else if (resolveFinish && running === 0) {
|
|
@@ -371,6 +642,7 @@ class Helpers {
|
|
|
371
642
|
operation: deserialize(bulkBody[i]),
|
|
372
643
|
document: operation !== 'delete'
|
|
373
644
|
? deserialize(bulkBody[i + 1])
|
|
645
|
+
/* istanbul ignore next */
|
|
374
646
|
: null,
|
|
375
647
|
retried: isRetrying
|
|
376
648
|
})
|
|
@@ -402,6 +674,7 @@ class Helpers {
|
|
|
402
674
|
// but the ES node were handling too many operations.
|
|
403
675
|
if (status === 429) {
|
|
404
676
|
retry.push(bulkBody[indexSlice])
|
|
677
|
+
/* istanbul ignore next */
|
|
405
678
|
if (operation !== 'delete') {
|
|
406
679
|
retry.push(bulkBody[indexSlice + 1])
|
|
407
680
|
}
|
|
@@ -428,4 +701,27 @@ class Helpers {
|
|
|
428
701
|
}
|
|
429
702
|
}
|
|
430
703
|
|
|
704
|
+
// Using a getter will improve the overall performances of the code,
|
|
705
|
+
// as we will reed the documents only if needed.
|
|
706
|
+
function addDocumentsGetter (result) {
|
|
707
|
+
Object.defineProperty(result, 'documents', {
|
|
708
|
+
get () {
|
|
709
|
+
if (this.body.hits && this.body.hits.hits) {
|
|
710
|
+
return this.body.hits.hits.map(d => d._source)
|
|
711
|
+
}
|
|
712
|
+
return []
|
|
713
|
+
}
|
|
714
|
+
})
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function appendFilterPath (filter, params, force) {
|
|
718
|
+
if (params.filter_path !== undefined) {
|
|
719
|
+
params.filter_path += ',' + filter
|
|
720
|
+
} else if (params.filterPath !== undefined) {
|
|
721
|
+
params.filterPath += ',' + filter
|
|
722
|
+
} else if (force === true) {
|
|
723
|
+
params.filter_path = filter
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
431
727
|
module.exports = Helpers
|
package/lib/Transport.js
CHANGED
|
@@ -22,7 +22,7 @@ const clientVersion = require('../package.json').version
|
|
|
22
22
|
const userAgent = `elasticsearch-js/${clientVersion} (${os.platform()} ${os.release()}-${os.arch()}; Node.js ${process.version})`
|
|
23
23
|
|
|
24
24
|
class Transport {
|
|
25
|
-
constructor (opts
|
|
25
|
+
constructor (opts) {
|
|
26
26
|
if (typeof opts.compression === 'string' && opts.compression !== 'gzip') {
|
|
27
27
|
throw new ConfigurationError(`Invalid compression: '${opts.compression}'`)
|
|
28
28
|
}
|
|
@@ -34,9 +34,9 @@ class Transport {
|
|
|
34
34
|
this.suggestCompression = opts.suggestCompression === true
|
|
35
35
|
this.compression = opts.compression || false
|
|
36
36
|
this.headers = Object.assign({},
|
|
37
|
-
{ '
|
|
38
|
-
opts.suggestCompression === true ? { '
|
|
39
|
-
opts.headers
|
|
37
|
+
{ 'user-agent': userAgent },
|
|
38
|
+
opts.suggestCompression === true ? { 'accept-encoding': 'gzip,deflate' } : null,
|
|
39
|
+
lowerCaseHeaders(opts.headers)
|
|
40
40
|
)
|
|
41
41
|
this.sniffInterval = opts.sniffInterval
|
|
42
42
|
this.sniffOnConnectionFault = opts.sniffOnConnectionFault
|
|
@@ -51,7 +51,6 @@ class Transport {
|
|
|
51
51
|
} else if (opts.nodeSelector === 'round-robin') {
|
|
52
52
|
this.nodeSelector = roundRobinSelector()
|
|
53
53
|
} else if (opts.nodeSelector === 'random') {
|
|
54
|
-
/* istanbul ignore next */
|
|
55
54
|
this.nodeSelector = randomSelector
|
|
56
55
|
} else {
|
|
57
56
|
this.nodeSelector = roundRobinSelector()
|
|
@@ -243,10 +242,10 @@ class Transport {
|
|
|
243
242
|
})
|
|
244
243
|
}
|
|
245
244
|
|
|
246
|
-
const headers = Object.assign({}, this.headers, options.headers)
|
|
245
|
+
const headers = Object.assign({}, this.headers, lowerCaseHeaders(options.headers))
|
|
247
246
|
|
|
248
247
|
if (options.opaqueId !== undefined) {
|
|
249
|
-
headers['
|
|
248
|
+
headers['x-opaque-id'] = this.opaqueIdPrefix !== null
|
|
250
249
|
? this.opaqueIdPrefix + options.opaqueId
|
|
251
250
|
: options.opaqueId
|
|
252
251
|
}
|
|
@@ -262,7 +261,7 @@ class Transport {
|
|
|
262
261
|
}
|
|
263
262
|
|
|
264
263
|
if (params.body !== '') {
|
|
265
|
-
headers['
|
|
264
|
+
headers['content-type'] = headers['content-type'] || 'application/json'
|
|
266
265
|
}
|
|
267
266
|
|
|
268
267
|
// handle ndjson body
|
|
@@ -277,7 +276,7 @@ class Transport {
|
|
|
277
276
|
params.body = params.bulkBody
|
|
278
277
|
}
|
|
279
278
|
if (params.body !== '') {
|
|
280
|
-
headers['
|
|
279
|
+
headers['content-type'] = headers['content-type'] || 'application/x-ndjson'
|
|
281
280
|
}
|
|
282
281
|
}
|
|
283
282
|
|
|
@@ -301,7 +300,7 @@ class Transport {
|
|
|
301
300
|
if (params.body !== '' && params.body != null) {
|
|
302
301
|
if (isStream(params.body) === true) {
|
|
303
302
|
if (compression === 'gzip') {
|
|
304
|
-
params.headers['
|
|
303
|
+
params.headers['content-encoding'] = compression
|
|
305
304
|
params.body = params.body.pipe(createGzip())
|
|
306
305
|
}
|
|
307
306
|
makeRequest()
|
|
@@ -311,13 +310,13 @@ class Transport {
|
|
|
311
310
|
if (err) {
|
|
312
311
|
return callback(err, result)
|
|
313
312
|
}
|
|
314
|
-
params.headers['
|
|
315
|
-
params.headers['
|
|
313
|
+
params.headers['content-encoding'] = compression
|
|
314
|
+
params.headers['content-length'] = '' + Buffer.byteLength(buffer)
|
|
316
315
|
params.body = buffer
|
|
317
316
|
makeRequest()
|
|
318
317
|
})
|
|
319
318
|
} else {
|
|
320
|
-
params.headers['
|
|
319
|
+
params.headers['content-length'] = '' + Buffer.byteLength(params.body)
|
|
321
320
|
makeRequest()
|
|
322
321
|
}
|
|
323
322
|
} else {
|
|
@@ -385,7 +384,7 @@ class Transport {
|
|
|
385
384
|
}
|
|
386
385
|
|
|
387
386
|
debug('Sniffing ended successfully', result.body)
|
|
388
|
-
const protocol = result.meta.connection.url.protocol || 'http:'
|
|
387
|
+
const protocol = result.meta.connection.url.protocol || /* istanbul ignore next */ 'http:'
|
|
389
388
|
const hosts = this.connectionPool.nodesToHost(result.body.nodes, protocol)
|
|
390
389
|
this.connectionPool.update(hosts)
|
|
391
390
|
|
|
@@ -453,5 +452,21 @@ function generateRequestId () {
|
|
|
453
452
|
return (nextReqId = (nextReqId + 1) & maxInt)
|
|
454
453
|
}
|
|
455
454
|
}
|
|
455
|
+
|
|
456
|
+
function lowerCaseHeaders (oldHeaders) {
|
|
457
|
+
if (oldHeaders == null) return oldHeaders
|
|
458
|
+
const newHeaders = {}
|
|
459
|
+
for (const header in oldHeaders) {
|
|
460
|
+
newHeaders[header.toLowerCase()] = oldHeaders[header]
|
|
461
|
+
}
|
|
462
|
+
return newHeaders
|
|
463
|
+
}
|
|
464
|
+
|
|
456
465
|
module.exports = Transport
|
|
457
|
-
module.exports.internals = {
|
|
466
|
+
module.exports.internals = {
|
|
467
|
+
defaultNodeFilter,
|
|
468
|
+
roundRobinSelector,
|
|
469
|
+
randomSelector,
|
|
470
|
+
generateRequestId,
|
|
471
|
+
lowerCaseHeaders
|
|
472
|
+
}
|
|
@@ -52,6 +52,7 @@ class BaseConnectionPool {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
if (opts.ssl == null) opts.ssl = this._ssl
|
|
55
|
+
/* istanbul ignore else */
|
|
55
56
|
if (opts.agent == null) opts.agent = this._agent
|
|
56
57
|
|
|
57
58
|
const connection = new this.Connection(opts)
|
|
@@ -201,6 +202,7 @@ class BaseConnectionPool {
|
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
address = address.slice(0, 4) === 'http'
|
|
205
|
+
/* istanbul ignore next */
|
|
204
206
|
? address
|
|
205
207
|
: `${protocol}//${address}`
|
|
206
208
|
const roles = node.roles.reduce((acc, role) => {
|