@ragestudio/scylla-odm 0.22.2 → 0.22.4

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.
Files changed (153) hide show
  1. package/batch/index.d.ts +3 -3
  2. package/batch/index.d.ts.map +1 -1
  3. package/client.d.ts +6 -5
  4. package/client.d.ts.map +1 -1
  5. package/client.js +11 -12
  6. package/client.js.map +1 -1
  7. package/cql_gen/create_table.d.ts +1 -1
  8. package/cql_gen/create_table.d.ts.map +1 -1
  9. package/document/index.d.ts +3 -3
  10. package/document/index.d.ts.map +1 -1
  11. package/driver/LICENSE.txt +177 -0
  12. package/driver/NOTICE.txt +67 -0
  13. package/driver/auth/index.d.ts +37 -0
  14. package/driver/auth/index.js +37 -0
  15. package/driver/auth/no-auth-provider.js +73 -0
  16. package/driver/auth/plain-text-auth-provider.js +81 -0
  17. package/driver/auth/provider.js +77 -0
  18. package/driver/client-options.js +442 -0
  19. package/driver/client.js +1267 -0
  20. package/driver/concurrent/index.d.ts +49 -0
  21. package/driver/concurrent/index.js +366 -0
  22. package/driver/connection.js +1034 -0
  23. package/driver/control-connection.js +1282 -0
  24. package/driver/encoder.js +2316 -0
  25. package/driver/errors.js +223 -0
  26. package/driver/execution-options.js +612 -0
  27. package/driver/execution-profile.js +274 -0
  28. package/driver/host-connection-pool.js +587 -0
  29. package/driver/host.js +699 -0
  30. package/driver/index.d.ts +387 -0
  31. package/driver/index.js +81 -0
  32. package/driver/mapping/cache.js +214 -0
  33. package/driver/mapping/doc-info-adapter.js +171 -0
  34. package/driver/mapping/index.d.ts +219 -0
  35. package/driver/mapping/index.js +57 -0
  36. package/driver/mapping/mapper.js +225 -0
  37. package/driver/mapping/mapping-handler.js +641 -0
  38. package/driver/mapping/model-batch-item.js +215 -0
  39. package/driver/mapping/model-batch-mapper.js +141 -0
  40. package/driver/mapping/model-mapper.js +315 -0
  41. package/driver/mapping/model-mapping-info.js +225 -0
  42. package/driver/mapping/object-selector.js +417 -0
  43. package/driver/mapping/q.js +156 -0
  44. package/driver/mapping/query-generator.js +556 -0
  45. package/driver/mapping/result-mapper.js +123 -0
  46. package/driver/mapping/result.js +139 -0
  47. package/driver/mapping/table-mappings.js +133 -0
  48. package/driver/mapping/tree.js +160 -0
  49. package/driver/metadata/aggregate.js +79 -0
  50. package/driver/metadata/client-state.js +119 -0
  51. package/driver/metadata/data-collection.js +182 -0
  52. package/driver/metadata/event-debouncer.js +174 -0
  53. package/driver/metadata/index.d.ts +276 -0
  54. package/driver/metadata/index.js +1156 -0
  55. package/driver/metadata/materialized-view.js +49 -0
  56. package/driver/metadata/schema-function.js +98 -0
  57. package/driver/metadata/schema-index.js +166 -0
  58. package/driver/metadata/schema-parser.js +1399 -0
  59. package/driver/metadata/table-metadata.js +77 -0
  60. package/driver/operation-state.js +206 -0
  61. package/driver/policies/address-resolution.js +145 -0
  62. package/driver/policies/index.d.ts +241 -0
  63. package/driver/policies/index.js +110 -0
  64. package/driver/policies/load-balancing.js +970 -0
  65. package/driver/policies/reconnection.js +166 -0
  66. package/driver/policies/retry.js +326 -0
  67. package/driver/policies/speculative-execution.js +150 -0
  68. package/driver/policies/timestamp-generation.js +176 -0
  69. package/driver/prepare-handler.js +347 -0
  70. package/driver/promise-utils.js +191 -0
  71. package/driver/readers.js +624 -0
  72. package/driver/request-execution.js +644 -0
  73. package/driver/request-handler.js +332 -0
  74. package/driver/requests.js +618 -0
  75. package/driver/stream-id-stack.js +209 -0
  76. package/driver/streams.js +745 -0
  77. package/driver/token.js +325 -0
  78. package/driver/tokenizer.js +631 -0
  79. package/driver/types/big-decimal.js +282 -0
  80. package/driver/types/duration.js +576 -0
  81. package/driver/types/index.d.ts +486 -0
  82. package/driver/types/index.js +733 -0
  83. package/driver/types/inet-address.js +262 -0
  84. package/driver/types/integer.js +818 -0
  85. package/driver/types/local-date.js +280 -0
  86. package/driver/types/local-time.js +299 -0
  87. package/driver/types/mutable-long.js +385 -0
  88. package/driver/types/protocol-version.js +391 -0
  89. package/driver/types/result-set.js +287 -0
  90. package/driver/types/result-stream.js +164 -0
  91. package/driver/types/row.js +85 -0
  92. package/driver/types/time-uuid.js +414 -0
  93. package/driver/types/tuple.js +103 -0
  94. package/driver/types/uuid.js +160 -0
  95. package/driver/types/vector.js +130 -0
  96. package/driver/types/version-number.js +153 -0
  97. package/driver/utils.js +1485 -0
  98. package/driver/writers.js +350 -0
  99. package/global.d.ts +1 -1
  100. package/global.d.ts.map +1 -1
  101. package/index.d.ts +6 -6
  102. package/index.d.ts.map +1 -1
  103. package/index.js +6 -6
  104. package/index.js.map +1 -1
  105. package/migrate/index.d.ts +1 -1
  106. package/migrate/index.d.ts.map +1 -1
  107. package/migrate/index.js +1 -1
  108. package/migrate/index.js.map +1 -1
  109. package/model/index.d.ts +6 -6
  110. package/model/index.d.ts.map +1 -1
  111. package/model/index.js +10 -10
  112. package/model/index.js.map +1 -1
  113. package/operations/countAll.d.ts +1 -1
  114. package/operations/countAll.d.ts.map +1 -1
  115. package/operations/delete.d.ts +3 -4
  116. package/operations/delete.d.ts.map +1 -1
  117. package/operations/delete.js +1 -1
  118. package/operations/delete.js.map +1 -1
  119. package/operations/find.d.ts +2 -2
  120. package/operations/find.d.ts.map +1 -1
  121. package/operations/find.js +1 -1
  122. package/operations/find.js.map +1 -1
  123. package/operations/findOne.d.ts +2 -2
  124. package/operations/findOne.d.ts.map +1 -1
  125. package/operations/findOne.js +1 -1
  126. package/operations/findOne.js.map +1 -1
  127. package/operations/insert.d.ts +3 -3
  128. package/operations/insert.d.ts.map +1 -1
  129. package/operations/insert.js +2 -2
  130. package/operations/insert.js.map +1 -1
  131. package/operations/sync.d.ts +1 -1
  132. package/operations/sync.d.ts.map +1 -1
  133. package/operations/sync.js +1 -1
  134. package/operations/sync.js.map +1 -1
  135. package/operations/tableExists.d.ts +1 -1
  136. package/operations/tableExists.d.ts.map +1 -1
  137. package/operations/update.d.ts +3 -3
  138. package/operations/update.d.ts.map +1 -1
  139. package/operations/update.js +2 -2
  140. package/operations/update.js.map +1 -1
  141. package/package.json +4 -12
  142. package/schema/index.d.ts +1 -1
  143. package/schema/index.d.ts.map +1 -1
  144. package/types.d.ts +4 -4
  145. package/types.d.ts.map +1 -1
  146. package/utils/queryParser.d.ts +1 -1
  147. package/utils/queryParser.d.ts.map +1 -1
  148. package/utils/queryParser.js +1 -1
  149. package/utils/queryParser.js.map +1 -1
  150. package/utils/typeChecker.d.ts +1 -1
  151. package/utils/typeChecker.d.ts.map +1 -1
  152. package/utils/typeChecker.js +1 -1
  153. package/utils/typeChecker.js.map +1 -1
@@ -0,0 +1,1282 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+ import events from "events"
19
+ import util from "util"
20
+ import net from "net"
21
+ import dns from "dns"
22
+
23
+ import errors from "./errors.js"
24
+ import { Host, HostMap } from "./host.js"
25
+ import Metadata from "./metadata/index.js"
26
+ import EventDebouncer from "./metadata/event-debouncer.js"
27
+ import Connection from "./connection.js"
28
+ import requests from "./requests.js"
29
+ import utils from "./utils.js"
30
+ import types from "./types/index.js"
31
+ import promiseUtils from "./promise-utils.js"
32
+
33
+ const f = util.format
34
+
35
+ const selectPeers = "SELECT * FROM system.peers"
36
+ const selectLocal = "SELECT * FROM system.local WHERE key='local'"
37
+ const newNodeDelay = 1000
38
+ const metadataQueryAbortTimeout = 2000
39
+ const schemaChangeTypes = {
40
+ created: "CREATED",
41
+ updated: "UPDATED",
42
+ dropped: "DROPPED",
43
+ }
44
+ const supportedProductTypeKey = "PRODUCT_TYPE"
45
+ const supportedDbaas = "DATASTAX_APOLLO"
46
+
47
+ /**
48
+ * Represents a connection used by the driver to receive events and to check the status of the cluster.
49
+ * <p>It uses an existing connection from the hosts' connection pool to maintain the driver metadata up-to-date.</p>
50
+ */
51
+ class ControlConnection extends events.EventEmitter {
52
+ /**
53
+ * Creates a new instance of <code>ControlConnection</code>.
54
+ * @param {Object} options
55
+ * @param {ProfileManager} profileManager
56
+ * @param {{borrowHostConnection: function, createConnection: function}} [context] An object containing methods to
57
+ * allow dependency injection.
58
+ */
59
+ constructor(options, profileManager, context) {
60
+ super()
61
+
62
+ this.protocolVersion = null
63
+ this.hosts = new HostMap()
64
+ this.setMaxListeners(0)
65
+ this.log = utils.log
66
+ Object.defineProperty(this, "options", {
67
+ value: options,
68
+ enumerable: false,
69
+ writable: false,
70
+ })
71
+
72
+ /**
73
+ * Cluster metadata that is going to be shared between the Client and ControlConnection
74
+ */
75
+ this.metadata = new Metadata(this.options, this)
76
+ this.initialized = false
77
+
78
+ /**
79
+ * Host used by the control connection
80
+ * @type {Host|null}
81
+ */
82
+ this.host = null
83
+
84
+ /**
85
+ * Connection used to retrieve metadata and subscribed to events
86
+ * @type {Connection|null}
87
+ */
88
+ this.connection = null
89
+
90
+ this._addressTranslator = this.options.policies.addressResolution
91
+ this._reconnectionPolicy = this.options.policies.reconnection
92
+ this._reconnectionSchedule = this._reconnectionPolicy.newSchedule()
93
+ this._isShuttingDown = false
94
+
95
+ // Reference to the encoder of the last valid connection
96
+ this._encoder = null
97
+ this._debouncer = new EventDebouncer(
98
+ options.refreshSchemaDelay,
99
+ this.log.bind(this),
100
+ )
101
+ this._profileManager = profileManager
102
+ this._triedHosts = null
103
+ this._resolvedContactPoints = new Map()
104
+ this._contactPoints = new Set()
105
+
106
+ // Timeout used for delayed handling of topology changes
107
+ this._topologyChangeTimeout = null
108
+ // Timeout used for delayed handling of node status changes
109
+ this._nodeStatusChangeTimeout = null
110
+
111
+ if (context && context.borrowHostConnection) {
112
+ this._borrowHostConnection = context.borrowHostConnection
113
+ }
114
+
115
+ if (context && context.createConnection) {
116
+ this._createConnection = context.createConnection
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Stores the contact point information and what it resolved to.
122
+ * @param {String|null} address
123
+ * @param {String} port
124
+ * @param {String} name
125
+ * @param {Boolean} isIPv6
126
+ */
127
+ _addContactPoint(address, port, name, isIPv6) {
128
+ if (address === null) {
129
+ // Contact point could not be resolved, store that the resolution came back empty
130
+ this._resolvedContactPoints.set(name, utils.emptyArray)
131
+ return
132
+ }
133
+
134
+ const portNumber =
135
+ parseInt(port, 10) || this.options.protocolOptions.port
136
+ const endpoint = `${address}:${portNumber}`
137
+ this._contactPoints.add(endpoint)
138
+
139
+ // Use RFC 3986 for IPv4 and IPv6
140
+ const standardEndpoint = !isIPv6
141
+ ? endpoint
142
+ : `[${address}]:${portNumber}`
143
+
144
+ let resolvedAddressedByName = this._resolvedContactPoints.get(name)
145
+
146
+ // NODEJS-646
147
+ //
148
+ // We might have a frozen empty array if DNS resolution wasn't working when this name was
149
+ // initially added, and if that's the case we can't add anything. Detect that case and
150
+ // reset to a mutable array.
151
+ if (
152
+ resolvedAddressedByName === undefined ||
153
+ resolvedAddressedByName === utils.emptyArray
154
+ ) {
155
+ resolvedAddressedByName = []
156
+ this._resolvedContactPoints.set(name, resolvedAddressedByName)
157
+ }
158
+
159
+ resolvedAddressedByName.push(standardEndpoint)
160
+ }
161
+
162
+ async _parseContactPoint(name) {
163
+ let addressOrName = name
164
+ let port = null
165
+
166
+ if (name.indexOf("[") === 0 && name.indexOf("]:") > 1) {
167
+ // IPv6 host notation [ip]:port (RFC 3986 section 3.2.2)
168
+ const index = name.lastIndexOf("]:")
169
+ addressOrName = name.substr(1, index - 1)
170
+ port = name.substr(index + 2)
171
+ } else if (name.indexOf(":") > 0) {
172
+ // IPv4 or host name with port notation
173
+ const parts = name.split(":")
174
+ if (parts.length === 2) {
175
+ addressOrName = parts[0]
176
+ port = parts[1]
177
+ }
178
+ }
179
+
180
+ if (net.isIP(addressOrName)) {
181
+ this._addContactPoint(
182
+ addressOrName,
183
+ port,
184
+ name,
185
+ net.isIPv6(addressOrName),
186
+ )
187
+ return
188
+ }
189
+
190
+ const addresses = await this._resolveAll(addressOrName)
191
+ if (addresses.length > 0) {
192
+ addresses.forEach((addressInfo) =>
193
+ this._addContactPoint(
194
+ addressInfo.address,
195
+ port,
196
+ name,
197
+ addressInfo.isIPv6,
198
+ ),
199
+ )
200
+ } else {
201
+ // Store that we attempted resolving the name but was not found
202
+ this._addContactPoint(null, null, name, false)
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Initializes the control connection by establishing a Connection using a suitable protocol
208
+ * version to be used and retrieving cluster metadata.
209
+ */
210
+ async init() {
211
+ if (this.initialized) {
212
+ // Prevent multiple serial initializations
213
+ return
214
+ }
215
+
216
+ if (!this.options.sni) {
217
+ // Parse and resolve contact points
218
+ await Promise.all(
219
+ this.options.contactPoints.map((name) =>
220
+ this._parseContactPoint(name),
221
+ ),
222
+ )
223
+ } else {
224
+ this.options.contactPoints.forEach((cp) =>
225
+ this._contactPoints.add(cp),
226
+ )
227
+ const address = this.options.sni.address
228
+ const separatorIndex = address.lastIndexOf(":")
229
+
230
+ if (separatorIndex === -1) {
231
+ throw new new errors.DriverInternalError(
232
+ "The SNI endpoint address should contain ip/name and port",
233
+ )()
234
+ }
235
+
236
+ const nameOrIp = address.substr(0, separatorIndex)
237
+ this.options.sni.port = address.substr(separatorIndex + 1)
238
+ this.options.sni.addressResolver = new utils.AddressResolver({
239
+ nameOrIp,
240
+ dns,
241
+ })
242
+ await this.options.sni.addressResolver.init()
243
+ }
244
+
245
+ if (this._contactPoints.size === 0) {
246
+ throw new errors.NoHostAvailableError(
247
+ {},
248
+ "No host could be resolved",
249
+ )
250
+ }
251
+
252
+ await this._initializeConnection()
253
+ }
254
+
255
+ _setHealthListeners(host, connection) {
256
+ const self = this
257
+ let wasRefreshCalled = 0
258
+
259
+ function removeListeners() {
260
+ host.removeListener("down", downOrIgnoredHandler)
261
+ host.removeListener("ignore", downOrIgnoredHandler)
262
+ connection.removeListener("socketClose", socketClosedHandler)
263
+ }
264
+
265
+ function startReconnecting(hostDown) {
266
+ if (wasRefreshCalled++ !== 0) {
267
+ // Prevent multiple calls to reconnect
268
+ return
269
+ }
270
+
271
+ removeListeners()
272
+
273
+ if (self._isShuttingDown) {
274
+ // Don't attempt to reconnect when the ControlConnection is being shutdown
275
+ return
276
+ }
277
+
278
+ if (hostDown) {
279
+ self.log(
280
+ "warning",
281
+ `Host ${host.address} used by the ControlConnection DOWN, ` +
282
+ `connection to ${connection.endpointFriendlyName} will not longer be used`,
283
+ )
284
+ } else {
285
+ self.log(
286
+ "warning",
287
+ `Connection to ${connection.endpointFriendlyName} used by the ControlConnection was closed`,
288
+ )
289
+ }
290
+
291
+ promiseUtils.toBackground(self._refresh())
292
+ }
293
+
294
+ function downOrIgnoredHandler() {
295
+ startReconnecting(true)
296
+ }
297
+
298
+ function socketClosedHandler() {
299
+ startReconnecting(false)
300
+ }
301
+
302
+ host.once("down", downOrIgnoredHandler)
303
+ host.once("ignore", downOrIgnoredHandler)
304
+ connection.once("socketClose", socketClosedHandler)
305
+ }
306
+
307
+ /**
308
+ * Iterates through the hostIterator and Gets the following open connection.
309
+ * @param {Iterator<Host>} hostIterator
310
+ * @returns {Connection!}
311
+ */
312
+ _borrowAConnection(hostIterator) {
313
+ let connection = null
314
+
315
+ while (!connection) {
316
+ const item = hostIterator.next()
317
+ const host = item.value
318
+
319
+ if (item.done) {
320
+ throw new errors.NoHostAvailableError(this._triedHosts)
321
+ }
322
+
323
+ // Only check distance once the load-balancing policies have been initialized
324
+ const distance = this._profileManager.getDistance(host)
325
+ if (!host.isUp() || distance === types.distance.ignored) {
326
+ continue
327
+ }
328
+
329
+ try {
330
+ connection = this._borrowHostConnection(host)
331
+ } catch (err) {
332
+ this._triedHosts[host.address] = err
333
+ }
334
+ }
335
+
336
+ return connection
337
+ }
338
+
339
+ /**
340
+ * Iterates through the contact points and tries to open a connection.
341
+ * @param {Iterator<string>} contactPointsIterator
342
+ * @returns {Promise<void>}
343
+ */
344
+ async _borrowFirstConnection(contactPointsIterator) {
345
+ let connection = null
346
+
347
+ while (!connection) {
348
+ const item = contactPointsIterator.next()
349
+ const contactPoint = item.value
350
+
351
+ if (item.done) {
352
+ throw new errors.NoHostAvailableError(this._triedHosts)
353
+ }
354
+
355
+ try {
356
+ connection = await this._createConnection(contactPoint)
357
+ } catch (err) {
358
+ this._triedHosts[contactPoint] = err
359
+ }
360
+ }
361
+
362
+ if (!connection) {
363
+ const err = new errors.NoHostAvailableError(this._triedHosts)
364
+ this.log(
365
+ "error",
366
+ "ControlConnection failed to acquire a connection",
367
+ )
368
+ throw err
369
+ }
370
+
371
+ this.protocolVersion = connection.protocolVersion
372
+ this._encoder = connection.encoder
373
+ this.connection = connection
374
+ }
375
+
376
+ /** Default implementation for borrowing connections, that can be injected at constructor level */
377
+ _borrowHostConnection(host) {
378
+ // Borrow any open connection, regardless of the keyspace
379
+ return host.borrowConnection()
380
+ }
381
+
382
+ /**
383
+ * Default implementation for creating initial connections, that can be injected at constructor level
384
+ * @param {String} contactPoint
385
+ */
386
+ async _createConnection(contactPoint) {
387
+ const c = new Connection(contactPoint, null, this.options)
388
+
389
+ try {
390
+ await c.openAsync()
391
+ } catch (err) {
392
+ promiseUtils.toBackground(c.closeAsync())
393
+ throw err
394
+ }
395
+
396
+ return c
397
+ }
398
+
399
+ /**
400
+ * Gets the info from local and peer metadata, reloads the keyspaces metadata and rebuilds tokens.
401
+ * <p>It throws an error when there's a failure or when reconnecting and there's no connection.</p>
402
+ * @param {Boolean} initializing Determines whether this function was called in order to initialize the control
403
+ * connection the first time
404
+ * @param {Boolean} isReconnecting Determines whether the refresh is being done because the ControlConnection is
405
+ * switching to use this connection to this host.
406
+ */
407
+ async _refreshHosts(initializing, isReconnecting) {
408
+ // Get a reference to the current connection as it might change from external events
409
+ const c = this.connection
410
+
411
+ if (!c) {
412
+ if (isReconnecting) {
413
+ throw new errors.DriverInternalError(
414
+ "Connection reference has been lost when reconnecting",
415
+ )
416
+ }
417
+
418
+ // it's possible that this was called as a result of a topology change, but the connection was lost
419
+ // between scheduling time and now. This will be called again when there is a new connection.
420
+ return
421
+ }
422
+
423
+ this.log("info", "Refreshing local and peers info")
424
+
425
+ const rsLocal = await c.send(
426
+ new requests.QueryRequest(selectLocal),
427
+ null,
428
+ )
429
+ this._setLocalInfo(initializing, isReconnecting, c, rsLocal)
430
+
431
+ if (!this.host) {
432
+ throw new errors.DriverInternalError(
433
+ "Information from system.local could not be retrieved",
434
+ )
435
+ }
436
+
437
+ const rsPeers = await c.send(
438
+ new requests.QueryRequest(selectPeers),
439
+ null,
440
+ )
441
+ await this.setPeersInfo(initializing, rsPeers)
442
+
443
+ if (!this.initialized) {
444
+ // resolve protocol version from highest common version among hosts.
445
+ const highestCommon = types.protocolVersion.getHighestCommon(
446
+ c,
447
+ this.hosts,
448
+ )
449
+ const reconnect = highestCommon !== this.protocolVersion
450
+
451
+ // set protocol version on each host.
452
+ this.protocolVersion = highestCommon
453
+ this.hosts.forEach((h) =>
454
+ h.setProtocolVersion(this.protocolVersion),
455
+ )
456
+
457
+ // if protocol version changed, reconnect the control connection with new version.
458
+ if (reconnect) {
459
+ this.log(
460
+ "info",
461
+ `Reconnecting since the protocol version changed to 0x${highestCommon.toString(16)}`,
462
+ )
463
+ c.decreaseVersion(this.protocolVersion)
464
+ await c.closeAsync()
465
+
466
+ try {
467
+ await c.openAsync()
468
+ } catch (err) {
469
+ // Close in the background
470
+ promiseUtils.toBackground(c.closeAsync())
471
+
472
+ throw err
473
+ }
474
+ }
475
+
476
+ // To acquire metadata we need to specify the cassandra version
477
+ this.metadata.setCassandraVersion(this.host.getCassandraVersion())
478
+ this.metadata.buildTokens(this.hosts)
479
+
480
+ if (!this.options.isMetadataSyncEnabled) {
481
+ this.metadata.initialized = true
482
+ return
483
+ }
484
+
485
+ await this.metadata.refreshKeyspacesInternal(false)
486
+ this.metadata.initialized = true
487
+ }
488
+ }
489
+
490
+ async _refreshControlConnection(hostIterator) {
491
+ if (this.options.sni) {
492
+ this.connection = this._borrowAConnection(hostIterator)
493
+ } else {
494
+ try {
495
+ this.connection = this._borrowAConnection(hostIterator)
496
+ } catch (err) {
497
+ /* NODEJS-632: refresh nodes before getting hosts for reconnect since some hostnames may have
498
+ * shifted during the flight. */
499
+ this.log(
500
+ "info",
501
+ "ControlConnection could not reconnect using existing connections. Refreshing contact points and retrying",
502
+ )
503
+ this._contactPoints.clear()
504
+ this._resolvedContactPoints.clear()
505
+ await Promise.all(
506
+ this.options.contactPoints.map((name) =>
507
+ this._parseContactPoint(name),
508
+ ),
509
+ )
510
+ const refreshedContactPoints = Array.from(
511
+ this._contactPoints,
512
+ ).join(",")
513
+ this.log(
514
+ "info",
515
+ `Refreshed contact points: ${refreshedContactPoints}`,
516
+ )
517
+ await this._initializeConnection()
518
+ }
519
+ }
520
+ }
521
+
522
+ /**
523
+ * Acquires a new connection and refreshes topology and keyspace metadata.
524
+ * <p>When it fails obtaining a connection and there aren't any more hosts, it schedules reconnection.</p>
525
+ * <p>When it fails obtaining the metadata, it marks connection and/or host unusable and retries using the same
526
+ * iterator from query plan / host list</p>
527
+ * @param {Iterator<Host>} [hostIterator]
528
+ */
529
+ async _refresh(hostIterator) {
530
+ if (this._isShuttingDown) {
531
+ this.log(
532
+ "info",
533
+ "The ControlConnection will not be refreshed as the Client is being shutdown",
534
+ )
535
+ return
536
+ }
537
+
538
+ // Reset host and connection
539
+ this.host = null
540
+ this.connection = null
541
+
542
+ try {
543
+ if (!hostIterator) {
544
+ this.log("info", "Trying to acquire a connection to a new host")
545
+ this._triedHosts = {}
546
+ hostIterator = await promiseUtils.newQueryPlan(
547
+ this._profileManager.getDefaultLoadBalancing(),
548
+ null,
549
+ null,
550
+ )
551
+ }
552
+
553
+ await this._refreshControlConnection(hostIterator)
554
+ } catch (err) {
555
+ // There was a failure obtaining a connection or during metadata retrieval
556
+ this.log(
557
+ "error",
558
+ "ControlConnection failed to acquire a connection",
559
+ err,
560
+ )
561
+
562
+ if (!this._isShuttingDown) {
563
+ const delay = this._reconnectionSchedule.next().value
564
+ this.log(
565
+ "warning",
566
+ `ControlConnection could not reconnect, scheduling reconnection in ${delay}ms`,
567
+ )
568
+ setTimeout(() => this._refresh(), delay)
569
+ this.emit("newConnection", err)
570
+ }
571
+
572
+ return
573
+ }
574
+
575
+ this.log(
576
+ "info",
577
+ `ControlConnection connected to ${this.connection.endpointFriendlyName}`,
578
+ )
579
+
580
+ try {
581
+ await this._refreshHosts(false, true)
582
+
583
+ await this._registerToConnectionEvents()
584
+ } catch (err) {
585
+ this.log(
586
+ "error",
587
+ "ControlConnection failed to retrieve topology and keyspaces information",
588
+ err,
589
+ )
590
+ this._triedHosts[this.connection.endpoint] = err
591
+
592
+ if (err.isSocketError && this.host) {
593
+ this.host.removeFromPool(this.connection)
594
+ }
595
+
596
+ // Retry the whole thing with the same query plan
597
+ return await this._refresh(hostIterator)
598
+ }
599
+
600
+ this._reconnectionSchedule = this._reconnectionPolicy.newSchedule()
601
+ this._setHealthListeners(this.host, this.connection)
602
+ this.emit("newConnection", null, this.connection, this.host)
603
+
604
+ this.log(
605
+ "info",
606
+ `ControlConnection connected to ${this.connection.endpointFriendlyName} and up to date`,
607
+ )
608
+ }
609
+
610
+ /**
611
+ * Acquires a connection and refreshes topology and keyspace metadata for the first time.
612
+ * @returns {Promise<void>}
613
+ */
614
+ async _initializeConnection() {
615
+ this.log("info", "Getting first connection")
616
+
617
+ // Reset host and connection
618
+ this.host = null
619
+ this.connection = null
620
+ this._triedHosts = {}
621
+
622
+ // Randomize order of contact points resolved.
623
+ const contactPointsIterator = utils
624
+ .shuffleArray(Array.from(this._contactPoints))
625
+ [Symbol.iterator]()
626
+
627
+ while (true) {
628
+ await this._borrowFirstConnection(contactPointsIterator)
629
+
630
+ this.log(
631
+ "info",
632
+ `ControlConnection using protocol version 0x${this.protocolVersion.toString(
633
+ 16,
634
+ )}, connected to ${this.connection.endpointFriendlyName}`,
635
+ )
636
+
637
+ try {
638
+ await this._getSupportedOptions()
639
+ await this._refreshHosts(true, true)
640
+ await this._registerToConnectionEvents()
641
+
642
+ // We have a valid connection, leave the loop
643
+ break
644
+ } catch (err) {
645
+ this.log(
646
+ "error",
647
+ "ControlConnection failed to retrieve topology and keyspaces information",
648
+ err,
649
+ )
650
+ this._triedHosts[this.connection.endpoint] = err
651
+ }
652
+ }
653
+
654
+ // The healthy connection used to initialize should be part of the Host pool
655
+ this.host.pool.addExistingConnection(this.connection)
656
+
657
+ this.initialized = true
658
+ this._setHealthListeners(this.host, this.connection)
659
+ this.log(
660
+ "info",
661
+ `ControlConnection connected to ${this.connection.endpointFriendlyName}`,
662
+ )
663
+ }
664
+
665
+ async _getSupportedOptions() {
666
+ const response = await this.connection.send(requests.options, null)
667
+
668
+ // response.supported is a string multi map, decoded as an Object.
669
+ const productType =
670
+ response.supported && response.supported[supportedProductTypeKey]
671
+ if (Array.isArray(productType) && productType[0] === supportedDbaas) {
672
+ this.metadata.setProductTypeAsDbaas()
673
+ }
674
+ }
675
+
676
+ async _registerToConnectionEvents() {
677
+ this.connection.on(
678
+ "nodeTopologyChange",
679
+ this._nodeTopologyChangeHandler.bind(this),
680
+ )
681
+ this.connection.on(
682
+ "nodeStatusChange",
683
+ this._nodeStatusChangeHandler.bind(this),
684
+ )
685
+ this.connection.on(
686
+ "nodeSchemaChange",
687
+ this._nodeSchemaChangeHandler.bind(this),
688
+ )
689
+ const request = new requests.RegisterRequest([
690
+ "TOPOLOGY_CHANGE",
691
+ "STATUS_CHANGE",
692
+ "SCHEMA_CHANGE",
693
+ ])
694
+ await this.connection.send(request, null)
695
+ }
696
+
697
+ /**
698
+ * Handles a TOPOLOGY_CHANGE event
699
+ */
700
+ _nodeTopologyChangeHandler(event) {
701
+ this.log("info", "Received topology change", event)
702
+
703
+ // all hosts information needs to be refreshed as tokens might have changed
704
+ clearTimeout(this._topologyChangeTimeout)
705
+
706
+ // Use an additional timer to make sure that the refresh hosts is executed only AFTER the delay
707
+ // In this case, the event debouncer doesn't help because it could not honor the sliding delay (ie: processNow)
708
+ this._topologyChangeTimeout = setTimeout(
709
+ () => promiseUtils.toBackground(this._scheduleRefreshHosts()),
710
+ newNodeDelay,
711
+ )
712
+ }
713
+
714
+ /**
715
+ * Handles a STATUS_CHANGE event
716
+ */
717
+ _nodeStatusChangeHandler(event) {
718
+ const self = this
719
+ const addressToTranslate = event.inet.address.toString()
720
+ const port = this.options.protocolOptions.port
721
+ this._addressTranslator.translate(
722
+ addressToTranslate,
723
+ port,
724
+ function translateCallback(endPoint) {
725
+ const host = self.hosts.get(endPoint)
726
+ if (!host) {
727
+ self.log(
728
+ "warning",
729
+ "Received status change event but host was not found: " +
730
+ addressToTranslate,
731
+ )
732
+ return
733
+ }
734
+ const distance = self._profileManager.getDistance(host)
735
+ if (event.up) {
736
+ if (distance === types.distance.ignored) {
737
+ return host.setUp(true)
738
+ }
739
+ clearTimeout(self._nodeStatusChangeTimeout)
740
+ // Waits a couple of seconds before marking it as UP
741
+ self._nodeStatusChangeTimeout = setTimeout(
742
+ () => host.checkIsUp(),
743
+ newNodeDelay,
744
+ )
745
+ return
746
+ }
747
+ // marked as down
748
+ if (distance === types.distance.ignored) {
749
+ return host.setDown()
750
+ }
751
+ self.log(
752
+ "warning",
753
+ "Received status change to DOWN for host " + host.address,
754
+ )
755
+ },
756
+ )
757
+ }
758
+
759
+ /**
760
+ * Handles a SCHEMA_CHANGE event
761
+ */
762
+ _nodeSchemaChangeHandler(event) {
763
+ this.log("info", "Schema change", event)
764
+ if (!this.options.isMetadataSyncEnabled) {
765
+ return
766
+ }
767
+
768
+ promiseUtils.toBackground(this.handleSchemaChange(event, false))
769
+ }
770
+
771
+ /**
772
+ * Schedules metadata refresh and callbacks when is refreshed.
773
+ * @param {{keyspace: string, isKeyspace: boolean, schemaChangeType, table, udt, functionName, aggregate}} event
774
+ * @param {Boolean} processNow
775
+ * @returns {Promise<void>}
776
+ */
777
+ handleSchemaChange(event, processNow) {
778
+ const self = this
779
+ let handler, cqlObject
780
+
781
+ if (event.isKeyspace) {
782
+ if (event.schemaChangeType === schemaChangeTypes.dropped) {
783
+ handler = function removeKeyspace() {
784
+ // if on the same event queue there is a creation, this handler is not going to be executed
785
+ // it is safe to remove the keyspace metadata
786
+ delete self.metadata.keyspaces[event.keyspace]
787
+ }
788
+
789
+ return this._scheduleObjectRefresh(
790
+ handler,
791
+ event.keyspace,
792
+ null,
793
+ processNow,
794
+ )
795
+ }
796
+
797
+ return this._scheduleKeyspaceRefresh(event.keyspace, processNow)
798
+ }
799
+
800
+ const ksInfo = this.metadata.keyspaces[event.keyspace]
801
+ if (!ksInfo) {
802
+ // it hasn't been loaded and it is not part of the metadata, don't mind
803
+ return Promise.resolve()
804
+ }
805
+
806
+ if (event.table) {
807
+ cqlObject = event.table
808
+ handler = function clearTableState() {
809
+ delete ksInfo.tables[event.table]
810
+ delete ksInfo.views[event.table]
811
+ }
812
+ } else if (event.udt) {
813
+ cqlObject = event.udt
814
+ handler = function clearUdtState() {
815
+ delete ksInfo.udts[event.udt]
816
+ }
817
+ } else if (event.functionName) {
818
+ cqlObject = event.functionName
819
+ handler = function clearFunctionState() {
820
+ delete ksInfo.functions[event.functionName]
821
+ }
822
+ } else if (event.aggregate) {
823
+ cqlObject = event.aggregate
824
+ handler = function clearKeyspaceState() {
825
+ delete ksInfo.aggregates[event.aggregate]
826
+ }
827
+ }
828
+
829
+ if (!handler) {
830
+ // Forward compatibility
831
+ return Promise.resolve()
832
+ }
833
+
834
+ // It's a cql object change clean the internal cache
835
+ return this._scheduleObjectRefresh(
836
+ handler,
837
+ event.keyspace,
838
+ cqlObject,
839
+ processNow,
840
+ )
841
+ }
842
+
843
+ /**
844
+ * @param {Function} handler
845
+ * @param {String} keyspace
846
+ * @param {String} cqlObject
847
+ * @param {Boolean} processNow
848
+ * @returns {Promise<void>}
849
+ */
850
+ _scheduleObjectRefresh(handler, keyspace, cqlObject, processNow) {
851
+ return this._debouncer.eventReceived(
852
+ { handler, keyspace, cqlObject },
853
+ processNow,
854
+ )
855
+ }
856
+
857
+ /**
858
+ * @param {String} keyspace
859
+ * @param {Boolean} processNow
860
+ * @returns {Promise<void>}
861
+ */
862
+ _scheduleKeyspaceRefresh(keyspace, processNow) {
863
+ return this._debouncer.eventReceived(
864
+ {
865
+ handler: () => this.metadata.refreshKeyspace(keyspace),
866
+ keyspace,
867
+ },
868
+ processNow,
869
+ )
870
+ }
871
+
872
+ /** @returns {Promise<void>} */
873
+ _scheduleRefreshHosts() {
874
+ return this._debouncer.eventReceived(
875
+ {
876
+ handler: () => this._refreshHosts(false, false),
877
+ all: true,
878
+ },
879
+ false,
880
+ )
881
+ }
882
+
883
+ /**
884
+ * Sets the information for the host used by the control connection.
885
+ * @param {Boolean} initializing
886
+ * @param {Connection} c
887
+ * @param {Boolean} setCurrentHost Determines if the host retrieved must be set as the current host
888
+ * @param result
889
+ */
890
+ _setLocalInfo(initializing, setCurrentHost, c, result) {
891
+ if (!result || !result.rows || !result.rows.length) {
892
+ this.log("warning", "No local info could be obtained")
893
+ return
894
+ }
895
+
896
+ const row = result.rows[0]
897
+
898
+ let localHost
899
+
900
+ // Note that with SNI enabled, we can trust that rpc_address will contain a valid value.
901
+ const endpoint = !this.options.sni
902
+ ? c.endpoint
903
+ : `${row["rpc_address"]}:${this.options.protocolOptions.port}`
904
+
905
+ if (initializing) {
906
+ localHost = new Host(
907
+ endpoint,
908
+ this.protocolVersion,
909
+ this.options,
910
+ this.metadata,
911
+ )
912
+ this.hosts.set(endpoint, localHost)
913
+ this.log("info", `Adding host ${endpoint}`)
914
+ } else {
915
+ localHost = this.hosts.get(endpoint)
916
+
917
+ if (!localHost) {
918
+ this.log("error", "Localhost could not be found")
919
+ return
920
+ }
921
+ }
922
+
923
+ localHost.datacenter = row["data_center"]
924
+ localHost.rack = row["rack"]
925
+ localHost.tokens = row["tokens"]
926
+ localHost.hostId = row["host_id"]
927
+ localHost.cassandraVersion = row["release_version"]
928
+ setDseParameters(localHost, row)
929
+ this.metadata.setPartitioner(row["partitioner"])
930
+ this.log("info", "Local info retrieved")
931
+
932
+ if (setCurrentHost) {
933
+ // Set the host as the one being used by the ControlConnection.
934
+ this.host = localHost
935
+ }
936
+ }
937
+
938
+ /**
939
+ * @param {Boolean} initializing Determines whether this function was called in order to initialize the control
940
+ * connection the first time.
941
+ * @param {ResultSet} result
942
+ */
943
+ async setPeersInfo(initializing, result) {
944
+ if (!result || !result.rows) {
945
+ return
946
+ }
947
+
948
+ // A map of peers, could useful for in case there are discrepancies
949
+ const peers = {}
950
+ const port = this.options.protocolOptions.port
951
+ const foundDataCenters = new Set()
952
+
953
+ if (this.host && this.host.datacenter) {
954
+ foundDataCenters.add(this.host.datacenter)
955
+ }
956
+
957
+ for (const row of result.rows) {
958
+ const endpoint = await this.getAddressForPeerHost(row, port)
959
+
960
+ if (!endpoint) {
961
+ continue
962
+ }
963
+
964
+ peers[endpoint] = true
965
+ let host = this.hosts.get(endpoint)
966
+ let isNewHost = !host
967
+
968
+ if (isNewHost) {
969
+ host = new Host(
970
+ endpoint,
971
+ this.protocolVersion,
972
+ this.options,
973
+ this.metadata,
974
+ )
975
+ this.log("info", `Adding host ${endpoint}`)
976
+ isNewHost = true
977
+ }
978
+
979
+ host.datacenter = row["data_center"]
980
+ host.rack = row["rack"]
981
+ host.tokens = row["tokens"]
982
+ host.hostId = row["host_id"]
983
+ host.cassandraVersion = row["release_version"]
984
+ setDseParameters(host, row)
985
+
986
+ if (host.datacenter) {
987
+ foundDataCenters.add(host.datacenter)
988
+ }
989
+
990
+ if (isNewHost) {
991
+ // Add it to the map (and trigger events) after all the properties
992
+ // were set to avoid race conditions
993
+ this.hosts.set(endpoint, host)
994
+
995
+ if (!initializing) {
996
+ // Set the distance at Host level, that way the connection pool is created with the correct settings
997
+ this._profileManager.getDistance(host)
998
+
999
+ // When we are not initializing, we start with the node set as DOWN
1000
+ host.setDown()
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+ // Is there a difference in number between peers + local != hosts
1006
+ if (this.hosts.length > result.rows.length + 1) {
1007
+ // There are hosts in the current state that don't belong (nodes removed or wrong contactPoints)
1008
+ this.log("info", "Removing nodes from the pool")
1009
+ const toRemove = []
1010
+
1011
+ this.hosts.forEach((h) => {
1012
+ //It is not a peer and it is not local host
1013
+ if (!peers[h.address] && h !== this.host) {
1014
+ this.log("info", "Removing host " + h.address)
1015
+ toRemove.push(h.address)
1016
+ h.shutdown(true)
1017
+ }
1018
+ })
1019
+
1020
+ this.hosts.removeMultiple(toRemove)
1021
+ }
1022
+
1023
+ if (initializing && this.options.localDataCenter) {
1024
+ const localDc = this.options.localDataCenter
1025
+
1026
+ if (!foundDataCenters.has(localDc)) {
1027
+ throw new errors.ArgumentError(
1028
+ `localDataCenter was configured as '${
1029
+ localDc
1030
+ }', but only found hosts in data centers: [${Array.from(foundDataCenters).join(", ")}]`,
1031
+ )
1032
+ }
1033
+ }
1034
+
1035
+ this.log("info", "Peers info retrieved")
1036
+ }
1037
+
1038
+ /**
1039
+ * Gets the address from a peers row and translates the address.
1040
+ * @param {Object|Row} row
1041
+ * @param {Number} defaultPort
1042
+ * @returns {Promise<string>}
1043
+ */
1044
+ getAddressForPeerHost(row, defaultPort) {
1045
+ return new Promise((resolve) => {
1046
+ let address = row["rpc_address"]
1047
+ const peer = row["peer"]
1048
+ const bindAllAddress = "0.0.0.0"
1049
+
1050
+ if (!address) {
1051
+ this.log(
1052
+ "error",
1053
+ f(
1054
+ "No rpc_address found for host %s in %s's peers system table. %s will be ignored.",
1055
+ peer,
1056
+ this.host.address,
1057
+ peer,
1058
+ ),
1059
+ )
1060
+ return resolve(null)
1061
+ }
1062
+
1063
+ if (address.toString() === bindAllAddress) {
1064
+ this.log(
1065
+ "warning",
1066
+ f(
1067
+ "Found host with 0.0.0.0 as rpc_address, using listen_address (%s) to contact it instead." +
1068
+ " If this is incorrect you should avoid the use of 0.0.0.0 server side.",
1069
+ peer,
1070
+ ),
1071
+ )
1072
+ address = peer
1073
+ }
1074
+
1075
+ this._addressTranslator.translate(
1076
+ address.toString(),
1077
+ defaultPort,
1078
+ resolve,
1079
+ )
1080
+ })
1081
+ }
1082
+
1083
+ /**
1084
+ * Uses the DNS protocol to resolve a IPv4 and IPv6 addresses (A and AAAA records) for the hostname.
1085
+ * It returns an Array of addresses that can be empty and logs the error.
1086
+ * @private
1087
+ * @param name
1088
+ */
1089
+ async _resolveAll(name) {
1090
+ const addresses = []
1091
+ const resolve4 = util.promisify(dns.resolve4)
1092
+ const resolve6 = util.promisify(dns.resolve6)
1093
+ const lookup = util.promisify(dns.lookup)
1094
+
1095
+ // Ignore errors for resolve calls
1096
+ const ipv4Promise = resolve4(name)
1097
+ .catch(() => {})
1098
+ .then((r) => r || utils.emptyArray)
1099
+ const ipv6Promise = resolve6(name)
1100
+ .catch(() => {})
1101
+ .then((r) => r || utils.emptyArray)
1102
+
1103
+ let arr
1104
+ arr = await ipv4Promise
1105
+ arr.forEach((address) => addresses.push({ address, isIPv6: false }))
1106
+
1107
+ arr = await ipv6Promise
1108
+ arr.forEach((address) => addresses.push({ address, isIPv6: true }))
1109
+
1110
+ if (addresses.length === 0) {
1111
+ // In case dns.resolve*() methods don't yield a valid address for the host name
1112
+ // Use system call getaddrinfo() that might resolve according to host system definitions
1113
+ try {
1114
+ arr = await lookup(name, { all: true })
1115
+ arr.forEach(({ address, family }) =>
1116
+ addresses.push({ address, isIPv6: family === 6 }),
1117
+ )
1118
+ } catch (err) {
1119
+ this.log(
1120
+ "error",
1121
+ `Host with name ${name} could not be resolved`,
1122
+ err,
1123
+ )
1124
+ }
1125
+ }
1126
+
1127
+ return addresses
1128
+ }
1129
+
1130
+ /**
1131
+ * Waits for a connection to be available. If timeout expires before getting a connection it callbacks in error.
1132
+ * @returns {Promise<void>}
1133
+ */
1134
+ _waitForReconnection() {
1135
+ return new Promise((resolve, reject) => {
1136
+ const callback = promiseUtils.getCallback(resolve, reject)
1137
+
1138
+ // eslint-disable-next-line prefer-const
1139
+ let timeout
1140
+
1141
+ function newConnectionListener(err) {
1142
+ clearTimeout(timeout)
1143
+ callback(err)
1144
+ }
1145
+
1146
+ this.once("newConnection", newConnectionListener)
1147
+
1148
+ timeout = setTimeout(() => {
1149
+ this.removeListener("newConnection", newConnectionListener)
1150
+ callback(
1151
+ new errors.OperationTimedOutError(
1152
+ "A connection could not be acquired before timeout.",
1153
+ ),
1154
+ )
1155
+ }, metadataQueryAbortTimeout)
1156
+ })
1157
+ }
1158
+
1159
+ /**
1160
+ * Executes a query using the active connection
1161
+ * @param {String|Request} cqlQuery
1162
+ * @param {Boolean} [waitReconnect] Determines if it should wait for reconnection in case the control connection is not
1163
+ * connected at the moment. Default: true.
1164
+ */
1165
+ async query(cqlQuery, waitReconnect = true) {
1166
+ const queryOnConnection = async () => {
1167
+ if (!this.connection || this._isShuttingDown) {
1168
+ throw new errors.NoHostAvailableError(
1169
+ {},
1170
+ "ControlConnection is not connected at the time",
1171
+ )
1172
+ }
1173
+
1174
+ const request =
1175
+ typeof cqlQuery === "string"
1176
+ ? new requests.QueryRequest(cqlQuery, null, null)
1177
+ : cqlQuery
1178
+ return await this.connection.send(request, null)
1179
+ }
1180
+
1181
+ if (!this.connection && waitReconnect) {
1182
+ // Wait until its reconnected (or timer elapses)
1183
+ await this._waitForReconnection()
1184
+ }
1185
+
1186
+ return await queryOnConnection()
1187
+ }
1188
+
1189
+ /** @returns {Encoder} The encoder used by the current connection */
1190
+ getEncoder() {
1191
+ if (!this._encoder) {
1192
+ throw new errors.DriverInternalError("Encoder is not defined")
1193
+ }
1194
+ return this._encoder
1195
+ }
1196
+
1197
+ /**
1198
+ * Cancels all timers and shuts down synchronously.
1199
+ */
1200
+ shutdown() {
1201
+ this._isShuttingDown = true
1202
+ this._debouncer.shutdown()
1203
+ // Emit a "newConnection" event with Error, as it may clear timeouts that were waiting new connections
1204
+ this.emit(
1205
+ "newConnection",
1206
+ new errors.DriverError("ControlConnection is being shutdown"),
1207
+ )
1208
+ // Cancel timers
1209
+ clearTimeout(this._topologyChangeTimeout)
1210
+ clearTimeout(this._nodeStatusChangeTimeout)
1211
+ }
1212
+
1213
+ /**
1214
+ * Resets the Connection to its initial state.
1215
+ */
1216
+ async reset() {
1217
+ // Reset the internal state of the ControlConnection for future initialization attempts
1218
+ const currentHosts = this.hosts.clear()
1219
+
1220
+ // Set the shutting down flag temporarily to avoid reconnects.
1221
+ this._isShuttingDown = true
1222
+
1223
+ // Shutdown all individual pools, ignoring any shutdown error
1224
+ await Promise.all(currentHosts.map((h) => h.shutdown()))
1225
+
1226
+ this.initialized = false
1227
+ this._isShuttingDown = false
1228
+ }
1229
+
1230
+ /**
1231
+ * Gets a Map containing the original contact points and the addresses that each one resolved to.
1232
+ */
1233
+ getResolvedContactPoints() {
1234
+ return this._resolvedContactPoints
1235
+ }
1236
+
1237
+ /**
1238
+ * Gets the local IP address to which the control connection socket is bound to.
1239
+ * @returns {String|undefined}
1240
+ */
1241
+ getLocalAddress() {
1242
+ if (!this.connection) {
1243
+ return undefined
1244
+ }
1245
+
1246
+ return this.connection.getLocalAddress()
1247
+ }
1248
+
1249
+ /**
1250
+ * Gets the address and port of host the control connection is connected to.
1251
+ * @returns {String|undefined}
1252
+ */
1253
+ getEndpoint() {
1254
+ if (!this.connection) {
1255
+ return undefined
1256
+ }
1257
+
1258
+ return this.connection.endpoint
1259
+ }
1260
+ }
1261
+
1262
+ /**
1263
+ * Parses the DSE workload and assigns it to a host.
1264
+ * @param {Host} host
1265
+ * @param {Row} row
1266
+ * @private
1267
+ */
1268
+ function setDseParameters(host, row) {
1269
+ if (row["workloads"] !== undefined) {
1270
+ host.workloads = row["workloads"]
1271
+ } else if (row["workload"]) {
1272
+ host.workloads = [row["workload"]]
1273
+ } else {
1274
+ host.workloads = utils.emptyArray
1275
+ }
1276
+
1277
+ if (row["dse_version"] !== undefined) {
1278
+ host.dseVersion = row["dse_version"]
1279
+ }
1280
+ }
1281
+
1282
+ export default ControlConnection