@ragestudio/scylla-odm 0.22.2 → 0.22.3

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 +7 -7
  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,1156 @@
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
+
19
+ import events from "events"
20
+ import util from "util"
21
+
22
+ /**
23
+ * Module containing classes and fields related to metadata.
24
+ * @module metadata
25
+ */
26
+
27
+ import t from "../tokenizer.js"
28
+ import utils from "../utils.js"
29
+ import errors from "../errors.js"
30
+ import types from "../types/index.js"
31
+ import requests from "../requests.js"
32
+ import schemaParserFactory from "./schema-parser.js"
33
+ import promiseUtils from "../promise-utils.js"
34
+ import { TokenRange } from "../token.js"
35
+ import { ExecutionOptions } from "../execution-options.js"
36
+
37
+ /**
38
+ * @const
39
+ * @private
40
+ */
41
+ const _selectTraceSession =
42
+ "SELECT * FROM system_traces.sessions WHERE session_id=%s"
43
+ /**
44
+ * @const
45
+ * @private
46
+ */
47
+ const _selectTraceEvents =
48
+ "SELECT * FROM system_traces.events WHERE session_id=%s"
49
+ /**
50
+ * @const
51
+ * @private
52
+ */
53
+ const _selectSchemaVersionPeers = "SELECT schema_version FROM system.peers"
54
+ /**
55
+ * @const
56
+ * @private
57
+ */
58
+ const _selectSchemaVersionLocal = "SELECT schema_version FROM system.local"
59
+ /**
60
+ * @const
61
+ * @private
62
+ */
63
+ const _traceMaxAttemps = 5
64
+ /**
65
+ * @const
66
+ * @private
67
+ */
68
+ const _traceAttemptDelay = 400
69
+
70
+ /**
71
+ * Represents cluster and schema information.
72
+ * The metadata class acts as a internal state of the driver.
73
+ */
74
+ class Metadata {
75
+ /**
76
+ * Creates a new instance of {@link Metadata}.
77
+ * @param {ClientOptions} options
78
+ * @param {ControlConnection} controlConnection Control connection used to retrieve information.
79
+ */
80
+ constructor(options, controlConnection) {
81
+ if (!options) {
82
+ throw new errors.ArgumentError("Options are not defined")
83
+ }
84
+
85
+ Object.defineProperty(this, "options", {
86
+ value: options,
87
+ enumerable: false,
88
+ writable: false,
89
+ })
90
+ Object.defineProperty(this, "controlConnection", {
91
+ value: controlConnection,
92
+ enumerable: false,
93
+ writable: false,
94
+ })
95
+ this.keyspaces = {}
96
+ this.initialized = false
97
+ this._isDbaas = false
98
+ this._schemaParser = schemaParserFactory.getByVersion(
99
+ options,
100
+ controlConnection,
101
+ this.getUdt.bind(this),
102
+ )
103
+ this.log = utils.log
104
+ this._preparedQueries = new PreparedQueries(
105
+ options.maxPrepared,
106
+ (...args) => this.log(...args),
107
+ )
108
+ }
109
+
110
+ /**
111
+ * Sets the cassandra version
112
+ * @internal
113
+ * @ignore
114
+ * @param {Array.<Number>} version
115
+ */
116
+ setCassandraVersion(version) {
117
+ this._schemaParser = schemaParserFactory.getByVersion(
118
+ this.options,
119
+ this.controlConnection,
120
+ this.getUdt.bind(this),
121
+ version,
122
+ this._schemaParser,
123
+ )
124
+ }
125
+
126
+ /**
127
+ * Determines whether the cluster is provided as a service.
128
+ * @returns {boolean} true when the cluster is provided as a service (DataStax Astra), <code>false<code> when it's a
129
+ * different deployment (on-prem).
130
+ */
131
+ isDbaas() {
132
+ return this._isDbaas
133
+ }
134
+
135
+ /**
136
+ * Sets the product type as DBaaS.
137
+ * @internal
138
+ * @ignore
139
+ */
140
+ setProductTypeAsDbaas() {
141
+ this._isDbaas = true
142
+ }
143
+
144
+ /**
145
+ * @ignore
146
+ * @param {String} partitionerName
147
+ */
148
+ setPartitioner(partitionerName) {
149
+ if (/RandomPartitioner$/.test(partitionerName)) {
150
+ return (this.tokenizer = new t.RandomTokenizer())
151
+ }
152
+ if (/ByteOrderedPartitioner$/.test(partitionerName)) {
153
+ return (this.tokenizer = new t.ByteOrderedTokenizer())
154
+ }
155
+ return (this.tokenizer = new t.Murmur3Tokenizer())
156
+ }
157
+
158
+ /**
159
+ * Populates the information regarding primary replica per token, datacenters (+ racks) and sorted token ring.
160
+ * @ignore
161
+ * @param {HostMap} hosts
162
+ */
163
+ buildTokens(hosts) {
164
+ if (!this.tokenizer) {
165
+ return this.log("error", "Tokenizer could not be determined")
166
+ }
167
+ //Get a sorted array of tokens
168
+ const allSorted = []
169
+ //Get a map of <token, primaryHost>
170
+ const primaryReplicas = {}
171
+ //Depending on the amount of tokens, this could be an expensive operation
172
+ const hostArray = hosts.values()
173
+ const stringify = this.tokenizer.stringify
174
+ const datacenters = {}
175
+ hostArray.forEach((h) => {
176
+ if (!h.tokens) {
177
+ return
178
+ }
179
+ h.tokens.forEach((tokenString) => {
180
+ const token = this.tokenizer.parse(tokenString)
181
+ utils.insertSorted(allSorted, token, (t1, t2) => t1.compare(t2))
182
+ primaryReplicas[stringify(token)] = h
183
+ })
184
+ let dc = datacenters[h.datacenter]
185
+ if (!dc) {
186
+ dc = datacenters[h.datacenter] = {
187
+ hostLength: 0,
188
+ racks: new utils.HashSet(),
189
+ }
190
+ }
191
+ dc.hostLength++
192
+ dc.racks.add(h.rack)
193
+ })
194
+ //Primary replica for given token
195
+ this.primaryReplicas = primaryReplicas
196
+ //All the tokens in ring order
197
+ this.ring = allSorted
198
+ // Build TokenRanges.
199
+ const tokenRanges = new Set()
200
+ if (this.ring.length === 1) {
201
+ // If there is only one token, return the range ]minToken, minToken]
202
+ const min = this.tokenizer.minToken()
203
+ tokenRanges.add(new TokenRange(min, min, this.tokenizer))
204
+ } else {
205
+ for (let i = 0; i < this.ring.length; i++) {
206
+ const start = this.ring[i]
207
+ const end = this.ring[(i + 1) % this.ring.length]
208
+ tokenRanges.add(new TokenRange(start, end, this.tokenizer))
209
+ }
210
+ }
211
+ this.tokenRanges = tokenRanges
212
+ //Compute string versions as it's potentially expensive and frequently reused later
213
+ this.ringTokensAsStrings = new Array(allSorted.length)
214
+ for (let i = 0; i < allSorted.length; i++) {
215
+ this.ringTokensAsStrings[i] = stringify(allSorted[i])
216
+ }
217
+ //Datacenter metadata (host length and racks)
218
+ this.datacenters = datacenters
219
+ }
220
+
221
+ /**
222
+ * Gets the keyspace metadata information and updates the internal state of the driver.
223
+ * <p>
224
+ * If a <code>callback</code> is provided, the callback is invoked when the keyspaces metadata refresh completes.
225
+ * Otherwise, it returns a <code>Promise</code>.
226
+ * </p>
227
+ * @param {String} name Name of the keyspace.
228
+ * @param {Function} [callback] Optional callback.
229
+ */
230
+ refreshKeyspace(name, callback) {
231
+ return promiseUtils.optionalCallback(
232
+ this._refreshKeyspace(name),
233
+ callback,
234
+ )
235
+ }
236
+
237
+ /**
238
+ * @param {String} name
239
+ * @private
240
+ */
241
+ async _refreshKeyspace(name) {
242
+ if (!this.initialized) {
243
+ throw this._uninitializedError()
244
+ }
245
+ this.log("info", util.format("Retrieving keyspace %s metadata", name))
246
+ try {
247
+ const ksInfo = await this._schemaParser.getKeyspace(name)
248
+ if (!ksInfo) {
249
+ // the keyspace was dropped
250
+ delete this.keyspaces[name]
251
+ return null
252
+ }
253
+ // Tokens are lazily init on the keyspace, once a replica from that keyspace is retrieved.
254
+ this.keyspaces[ksInfo.name] = ksInfo
255
+ return ksInfo
256
+ } catch (err) {
257
+ this.log(
258
+ "error",
259
+ "There was an error while trying to retrieve keyspace information",
260
+ err,
261
+ )
262
+ throw err
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Gets the metadata information of all the keyspaces and updates the internal state of the driver.
268
+ * <p>
269
+ * If a <code>callback</code> is provided, the callback is invoked when the keyspace metadata refresh completes.
270
+ * Otherwise, it returns a <code>Promise</code>.
271
+ * </p>
272
+ * @param {Boolean|Function} [waitReconnect] Determines if it should wait for reconnection in case the control connection is not
273
+ * connected at the moment. Default: true.
274
+ * @param {Function} [callback] Optional callback.
275
+ */
276
+ refreshKeyspaces(waitReconnect, callback) {
277
+ if (
278
+ typeof waitReconnect === "function" ||
279
+ typeof waitReconnect === "undefined"
280
+ ) {
281
+ callback = waitReconnect
282
+ waitReconnect = true
283
+ }
284
+ if (!this.initialized) {
285
+ const err = this._uninitializedError()
286
+ if (callback) {
287
+ return callback(err)
288
+ }
289
+ return Promise.reject(err)
290
+ }
291
+ return promiseUtils.optionalCallback(
292
+ this.refreshKeyspacesInternal(waitReconnect),
293
+ callback,
294
+ )
295
+ }
296
+
297
+ /**
298
+ * @param {Boolean} waitReconnect
299
+ * @returns {Promise<Object<string, Object>>}
300
+ * @ignore
301
+ * @internal
302
+ */
303
+ async refreshKeyspacesInternal(waitReconnect) {
304
+ this.log("info", "Retrieving keyspaces metadata")
305
+ try {
306
+ this.keyspaces =
307
+ await this._schemaParser.getKeyspaces(waitReconnect)
308
+ return this.keyspaces
309
+ } catch (err) {
310
+ this.log(
311
+ "error",
312
+ "There was an error while trying to retrieve keyspaces information",
313
+ err,
314
+ )
315
+ throw err
316
+ }
317
+ }
318
+
319
+ _getKeyspaceReplicas(keyspace) {
320
+ if (!keyspace.replicas) {
321
+ //Calculate replicas the first time for the keyspace
322
+ keyspace.replicas = keyspace.tokenToReplica(
323
+ this.tokenizer,
324
+ this.ringTokensAsStrings,
325
+ this.primaryReplicas,
326
+ this.datacenters,
327
+ )
328
+ }
329
+ return keyspace.replicas
330
+ }
331
+
332
+ /**
333
+ * Gets the host list representing the replicas that contain the given partition key, token or token range.
334
+ * <p>
335
+ * It uses the pre-loaded keyspace metadata to retrieve the replicas for a token for a given keyspace.
336
+ * When the keyspace metadata has not been loaded, it returns null.
337
+ * </p>
338
+ * @param {String} keyspaceName
339
+ * @param {Buffer|Token|TokenRange} token Can be Buffer (serialized partition key), Token or TokenRange
340
+ * @returns {Array}
341
+ */
342
+ getReplicas(keyspaceName, token) {
343
+ if (!this.ring) {
344
+ return null
345
+ }
346
+ if (Buffer.isBuffer(token)) {
347
+ token = this.tokenizer.hash(token)
348
+ }
349
+ if (token instanceof TokenRange) {
350
+ token = token.end
351
+ }
352
+ let keyspace
353
+ if (keyspaceName) {
354
+ keyspace = this.keyspaces[keyspaceName]
355
+ if (!keyspace) {
356
+ // the keyspace was not found, the metadata should be loaded beforehand
357
+ return null
358
+ }
359
+ }
360
+ let i = utils.binarySearch(this.ring, token, (t1, t2) => t1.compare(t2))
361
+ if (i < 0) {
362
+ i = ~i
363
+ }
364
+ if (i >= this.ring.length) {
365
+ //it circled back
366
+ i = i % this.ring.length
367
+ }
368
+ const closestToken = this.ringTokensAsStrings[i]
369
+ if (!keyspaceName) {
370
+ return [this.primaryReplicas[closestToken]]
371
+ }
372
+ const replicas = this._getKeyspaceReplicas(keyspace)
373
+ return replicas[closestToken]
374
+ }
375
+
376
+ /**
377
+ * Gets the token ranges that define data distribution in the ring.
378
+ *
379
+ * @returns {Set<TokenRange>} The ranges of the ring or empty set if schema metadata is not enabled.
380
+ */
381
+ getTokenRanges() {
382
+ return this.tokenRanges
383
+ }
384
+
385
+ /**
386
+ * Gets the token ranges that are replicated on the given host, for
387
+ * the given keyspace.
388
+ *
389
+ * @param {String} keyspaceName The name of the keyspace to get ranges for.
390
+ * @param {Host} host The host.
391
+ * @returns {Set<TokenRange>|null} Ranges for the keyspace on this host or null if keyspace isn't found or hasn't been loaded.
392
+ */
393
+ getTokenRangesForHost(keyspaceName, host) {
394
+ if (!this.ring) {
395
+ return null
396
+ }
397
+ let keyspace
398
+ if (keyspaceName) {
399
+ keyspace = this.keyspaces[keyspaceName]
400
+ if (!keyspace) {
401
+ // the keyspace was not found, the metadata should be loaded beforehand
402
+ return null
403
+ }
404
+ }
405
+ // If the ring has only 1 token, just return the ranges as we should only have a single node cluster.
406
+ if (this.ring.length === 1) {
407
+ return this.getTokenRanges()
408
+ }
409
+ const replicas = this._getKeyspaceReplicas(keyspace)
410
+ const ranges = new Set()
411
+ // for each range, find replicas for end token, if replicas include host, add range.
412
+ this.tokenRanges.forEach((tokenRange) => {
413
+ const replicasForToken =
414
+ replicas[this.tokenizer.stringify(tokenRange.end)]
415
+ if (replicasForToken.indexOf(host) !== -1) {
416
+ ranges.add(tokenRange)
417
+ }
418
+ })
419
+ return ranges
420
+ }
421
+
422
+ /**
423
+ * Constructs a Token from the input buffer(s) or string input. If a string is passed in
424
+ * it is assumed this matches the token representation reported by cassandra.
425
+ * @param {Array<Buffer>|Buffer|String} components
426
+ * @returns {Token} constructed token from the input buffer.
427
+ */
428
+ newToken(components) {
429
+ if (!this.tokenizer) {
430
+ throw new Error(
431
+ "Partitioner not established. This should only happen if metadata was disabled or you have not connected yet.",
432
+ )
433
+ }
434
+ if (Array.isArray(components)) {
435
+ return this.tokenizer.hash(Buffer.concat(components))
436
+ } else if (typeof components === "string") {
437
+ return this.tokenizer.parse(components)
438
+ }
439
+ return this.tokenizer.hash(components)
440
+ }
441
+
442
+ /**
443
+ * Constructs a TokenRange from the given start and end tokens.
444
+ * @param {Token} start
445
+ * @param {Token} end
446
+ * @returns TokenRange build range spanning from start (exclusive) to end (inclusive).
447
+ */
448
+ newTokenRange(start, end) {
449
+ if (!this.tokenizer) {
450
+ throw new Error(
451
+ "Partitioner not established. This should only happen if metadata was disabled or you have not connected yet.",
452
+ )
453
+ }
454
+ return new TokenRange(start, end, this.tokenizer)
455
+ }
456
+
457
+ /**
458
+ * Gets the metadata information already stored associated to a prepared statement
459
+ * @param {String} keyspaceName
460
+ * @param {String} query
461
+ * @internal
462
+ * @ignore
463
+ */
464
+ getPreparedInfo(keyspaceName, query) {
465
+ return this._preparedQueries.getOrAdd(keyspaceName, query)
466
+ }
467
+
468
+ /**
469
+ * Clears the internal state related to the prepared statements.
470
+ * Following calls to the Client using the prepare flag will re-prepare the statements.
471
+ */
472
+ clearPrepared() {
473
+ this._preparedQueries.clear()
474
+ }
475
+
476
+ /** @ignore */
477
+ getPreparedById(id) {
478
+ return this._preparedQueries.getById(id)
479
+ }
480
+
481
+ /** @ignore */
482
+ setPreparedById(info) {
483
+ return this._preparedQueries.setById(info)
484
+ }
485
+
486
+ /** @ignore */
487
+ getAllPrepared() {
488
+ return this._preparedQueries.getAll()
489
+ }
490
+
491
+ /** @ignore */
492
+ _uninitializedError() {
493
+ return new Error(
494
+ "Metadata has not been initialized. This could only happen if you have not connected yet.",
495
+ )
496
+ }
497
+
498
+ /**
499
+ * Gets the definition of an user-defined type.
500
+ * <p>
501
+ * If a <code>callback</code> is provided, the callback is invoked when the metadata retrieval completes.
502
+ * Otherwise, it returns a <code>Promise</code>.
503
+ * </p>
504
+ * <p>
505
+ * When trying to retrieve the same UDT definition concurrently, it will query once and invoke all callbacks
506
+ * with the retrieved information.
507
+ * </p>
508
+ * @param {String} keyspaceName Name of the keyspace.
509
+ * @param {String} name Name of the UDT.
510
+ * @param {Function} [callback] The callback to invoke when retrieval completes.
511
+ */
512
+ getUdt(keyspaceName, name, callback) {
513
+ return promiseUtils.optionalCallback(
514
+ this._getUdt(keyspaceName, name),
515
+ callback,
516
+ )
517
+ }
518
+
519
+ /**
520
+ * @param {String} keyspaceName
521
+ * @param {String} name
522
+ * @returns {Promise<Object|null>}
523
+ * @private
524
+ */
525
+ async _getUdt(keyspaceName, name) {
526
+ if (!this.initialized) {
527
+ throw this._uninitializedError()
528
+ }
529
+ let cache
530
+ if (this.options.isMetadataSyncEnabled) {
531
+ const keyspace = this.keyspaces[keyspaceName]
532
+ if (!keyspace) {
533
+ return null
534
+ }
535
+ cache = keyspace.udts
536
+ }
537
+ return await this._schemaParser.getUdt(keyspaceName, name, cache)
538
+ }
539
+
540
+ /**
541
+ * Gets the definition of a table.
542
+ * <p>
543
+ * If a <code>callback</code> is provided, the callback is invoked when the metadata retrieval completes.
544
+ * Otherwise, it returns a <code>Promise</code>.
545
+ * </p>
546
+ * <p>
547
+ * When trying to retrieve the same table definition concurrently, it will query once and invoke all callbacks
548
+ * with the retrieved information.
549
+ * </p>
550
+ * @param {String} keyspaceName Name of the keyspace.
551
+ * @param {String} name Name of the Table.
552
+ * @param {Function} [callback] The callback with the err as a first parameter and the {@link TableMetadata} as
553
+ * second parameter.
554
+ */
555
+ getTable(keyspaceName, name, callback) {
556
+ return promiseUtils.optionalCallback(
557
+ this._getTable(keyspaceName, name),
558
+ callback,
559
+ )
560
+ }
561
+
562
+ /**
563
+ * @param {String} keyspaceName
564
+ * @param {String} name
565
+ * @private
566
+ */
567
+ async _getTable(keyspaceName, name) {
568
+ if (!this.initialized) {
569
+ throw this._uninitializedError()
570
+ }
571
+ let cache
572
+ let virtual
573
+ if (this.options.isMetadataSyncEnabled) {
574
+ const keyspace = this.keyspaces[keyspaceName]
575
+ if (!keyspace) {
576
+ return null
577
+ }
578
+ cache = keyspace.tables
579
+ virtual = keyspace.virtual
580
+ }
581
+ return await this._schemaParser.getTable(
582
+ keyspaceName,
583
+ name,
584
+ cache,
585
+ virtual,
586
+ )
587
+ }
588
+
589
+ /**
590
+ * Gets the definition of CQL functions for a given name.
591
+ * <p>
592
+ * If a <code>callback</code> is provided, the callback is invoked when the metadata retrieval completes.
593
+ * Otherwise, it returns a <code>Promise</code>.
594
+ * </p>
595
+ * <p>
596
+ * When trying to retrieve the same function definition concurrently, it will query once and invoke all callbacks
597
+ * with the retrieved information.
598
+ * </p>
599
+ * @param {String} keyspaceName Name of the keyspace.
600
+ * @param {String} name Name of the Function.
601
+ * @param {Function} [callback] The callback with the err as a first parameter and the array of {@link SchemaFunction}
602
+ * as second parameter.
603
+ */
604
+ getFunctions(keyspaceName, name, callback) {
605
+ return promiseUtils.optionalCallback(
606
+ this._getFunctionsWrapper(keyspaceName, name),
607
+ callback,
608
+ )
609
+ }
610
+
611
+ /**
612
+ * @param {String} keyspaceName
613
+ * @param {String} name
614
+ * @private
615
+ */
616
+ async _getFunctionsWrapper(keyspaceName, name) {
617
+ if (!keyspaceName || !name) {
618
+ throw new errors.ArgumentError(
619
+ "You must provide the keyspace name and cql function name to retrieve the metadata",
620
+ )
621
+ }
622
+ const functionsMap = await this._getFunctions(keyspaceName, name, false)
623
+ return Array.from(functionsMap.values())
624
+ }
625
+
626
+ /**
627
+ * Gets a definition of CQL function for a given name and signature.
628
+ * <p>
629
+ * If a <code>callback</code> is provided, the callback is invoked when the metadata retrieval completes.
630
+ * Otherwise, it returns a <code>Promise</code>.
631
+ * </p>
632
+ * <p>
633
+ * When trying to retrieve the same function definition concurrently, it will query once and invoke all callbacks
634
+ * with the retrieved information.
635
+ * </p>
636
+ * @param {String} keyspaceName Name of the keyspace
637
+ * @param {String} name Name of the Function
638
+ * @param {Array.<String>|Array.<{code, info}>} signature Array of types of the parameters.
639
+ * @param {Function} [callback] The callback with the err as a first parameter and the {@link SchemaFunction} as second
640
+ * parameter.
641
+ */
642
+ getFunction(keyspaceName, name, signature, callback) {
643
+ return promiseUtils.optionalCallback(
644
+ this._getSingleFunction(keyspaceName, name, signature, false),
645
+ callback,
646
+ )
647
+ }
648
+
649
+ /**
650
+ * Gets the definition of CQL aggregate for a given name.
651
+ * <p>
652
+ * If a <code>callback</code> is provided, the callback is invoked when the metadata retrieval completes.
653
+ * Otherwise, it returns a <code>Promise</code>.
654
+ * </p>
655
+ * <p>
656
+ * When trying to retrieve the same aggregates definition concurrently, it will query once and invoke all callbacks
657
+ * with the retrieved information.
658
+ * </p>
659
+ * @param {String} keyspaceName Name of the keyspace
660
+ * @param {String} name Name of the Function
661
+ * @param {Function} [callback] The callback with the err as a first parameter and the array of {@link Aggregate} as
662
+ * second parameter.
663
+ */
664
+ getAggregates(keyspaceName, name, callback) {
665
+ return promiseUtils.optionalCallback(
666
+ this._getAggregates(keyspaceName, name),
667
+ callback,
668
+ )
669
+ }
670
+
671
+ /**
672
+ * @param {String} keyspaceName
673
+ * @param {String} name
674
+ * @private
675
+ */
676
+ async _getAggregates(keyspaceName, name) {
677
+ if (!keyspaceName || !name) {
678
+ throw new errors.ArgumentError(
679
+ "You must provide the keyspace name and cql aggregate name to retrieve the metadata",
680
+ )
681
+ }
682
+ const functionsMap = await this._getFunctions(keyspaceName, name, true)
683
+ return Array.from(functionsMap.values())
684
+ }
685
+
686
+ /**
687
+ * Gets a definition of CQL aggregate for a given name and signature.
688
+ * <p>
689
+ * If a <code>callback</code> is provided, the callback is invoked when the metadata retrieval completes.
690
+ * Otherwise, it returns a <code>Promise</code>.
691
+ * </p>
692
+ * <p>
693
+ * When trying to retrieve the same aggregate definition concurrently, it will query once and invoke all callbacks
694
+ * with the retrieved information.
695
+ * </p>
696
+ * @param {String} keyspaceName Name of the keyspace
697
+ * @param {String} name Name of the aggregate
698
+ * @param {Array.<String>|Array.<{code, info}>} signature Array of types of the parameters.
699
+ * @param {Function} [callback] The callback with the err as a first parameter and the {@link Aggregate} as second parameter.
700
+ */
701
+ getAggregate(keyspaceName, name, signature, callback) {
702
+ return promiseUtils.optionalCallback(
703
+ this._getSingleFunction(keyspaceName, name, signature, true),
704
+ callback,
705
+ )
706
+ }
707
+
708
+ /**
709
+ * Gets the definition of a CQL materialized view for a given name.
710
+ * <p>
711
+ * If a <code>callback</code> is provided, the callback is invoked when the metadata retrieval completes.
712
+ * Otherwise, it returns a <code>Promise</code>.
713
+ * </p>
714
+ * <p>
715
+ * Note that, unlike the rest of the {@link Metadata} methods, this method does not cache the result for following
716
+ * calls, as the current version of the Cassandra native protocol does not support schema change events for
717
+ * materialized views. Each call to this method will produce one or more queries to the cluster.
718
+ * </p>
719
+ * @param {String} keyspaceName Name of the keyspace
720
+ * @param {String} name Name of the materialized view
721
+ * @param {Function} [callback] The callback with the err as a first parameter and the {@link MaterializedView} as
722
+ * second parameter.
723
+ */
724
+ getMaterializedView(keyspaceName, name, callback) {
725
+ return promiseUtils.optionalCallback(
726
+ this._getMaterializedView(keyspaceName, name),
727
+ callback,
728
+ )
729
+ }
730
+
731
+ /**
732
+ * @param {String} keyspaceName
733
+ * @param {String} name
734
+ * @returns {Promise<MaterializedView|null>}
735
+ * @private
736
+ */
737
+ async _getMaterializedView(keyspaceName, name) {
738
+ if (!this.initialized) {
739
+ throw this._uninitializedError()
740
+ }
741
+ let cache
742
+ if (this.options.isMetadataSyncEnabled) {
743
+ const keyspace = this.keyspaces[keyspaceName]
744
+ if (!keyspace) {
745
+ return null
746
+ }
747
+ cache = keyspace.views
748
+ }
749
+ return await this._schemaParser.getMaterializedView(
750
+ keyspaceName,
751
+ name,
752
+ cache,
753
+ )
754
+ }
755
+
756
+ /**
757
+ * Gets a map of cql function definitions or aggregates based on signature.
758
+ * @param {String} keyspaceName
759
+ * @param {String} name Name of the function or aggregate
760
+ * @param {Boolean} aggregate
761
+ * @returns {Promise<Map>}
762
+ * @private
763
+ */
764
+ async _getFunctions(keyspaceName, name, aggregate) {
765
+ if (!this.initialized) {
766
+ throw this._uninitializedError()
767
+ }
768
+ let cache
769
+ if (this.options.isMetadataSyncEnabled) {
770
+ const keyspace = this.keyspaces[keyspaceName]
771
+ if (!keyspace) {
772
+ return new Map()
773
+ }
774
+ cache = aggregate ? keyspace.aggregates : keyspace.functions
775
+ }
776
+ return await this._schemaParser.getFunctions(
777
+ keyspaceName,
778
+ name,
779
+ aggregate,
780
+ cache,
781
+ )
782
+ }
783
+
784
+ /**
785
+ * Gets a single cql function or aggregate definition
786
+ * @param {String} keyspaceName
787
+ * @param {String} name
788
+ * @param {Array} signature
789
+ * @param {Boolean} aggregate
790
+ * @returns {Promise<SchemaFunction|Aggregate|null>}
791
+ * @private
792
+ */
793
+ async _getSingleFunction(keyspaceName, name, signature, aggregate) {
794
+ if (!keyspaceName || !name) {
795
+ throw new errors.ArgumentError(
796
+ "You must provide the keyspace name and cql function name to retrieve the metadata",
797
+ )
798
+ }
799
+ if (!Array.isArray(signature)) {
800
+ throw new errors.ArgumentError(
801
+ "Signature must be an array of types",
802
+ )
803
+ }
804
+ signature = signature.map((item) => {
805
+ if (typeof item === "string") {
806
+ return item
807
+ }
808
+ return types.getDataTypeNameByCode(item)
809
+ })
810
+ const functionsMap = await this._getFunctions(
811
+ keyspaceName,
812
+ name,
813
+ aggregate,
814
+ )
815
+ return functionsMap.get(signature.join(",")) || null
816
+ }
817
+
818
+ /**
819
+ * Gets the trace session generated by Cassandra when query tracing is enabled for the
820
+ * query. The trace itself is stored in Cassandra in the <code>sessions</code> and
821
+ * <code>events</code> table in the <code>system_traces</code> keyspace and can be
822
+ * retrieve manually using the trace identifier.
823
+ * <p>
824
+ * If a <code>callback</code> is provided, the callback is invoked when the metadata retrieval completes.
825
+ * Otherwise, it returns a <code>Promise</code>.
826
+ * </p>
827
+ * @param {Uuid} traceId Identifier of the trace session.
828
+ * @param {Number} [consistency] The consistency level to obtain the trace.
829
+ * @param {Function} [callback] The callback with the err as first parameter and the query trace as second parameter.
830
+ */
831
+ getTrace(traceId, consistency, callback) {
832
+ if (!callback && typeof consistency === "function") {
833
+ // Both callback and consistency are optional parameters
834
+ // In this case, the second parameter is the callback
835
+ callback = consistency
836
+ consistency = null
837
+ }
838
+
839
+ return promiseUtils.optionalCallback(
840
+ this._getTrace(traceId, consistency),
841
+ callback,
842
+ )
843
+ }
844
+
845
+ /**
846
+ * @param {Uuid} traceId
847
+ * @param {Number} consistency
848
+ * @returns {Promise<Object>}
849
+ * @private
850
+ */
851
+ async _getTrace(traceId, consistency) {
852
+ if (!this.initialized) {
853
+ throw this._uninitializedError()
854
+ }
855
+
856
+ let trace
857
+ let attempts = 0
858
+ const info = ExecutionOptions.empty()
859
+ info.getConsistency = () => consistency
860
+
861
+ const sessionRequest = new requests.QueryRequest(
862
+ util.format(_selectTraceSession, traceId),
863
+ null,
864
+ info,
865
+ )
866
+ const eventsRequest = new requests.QueryRequest(
867
+ util.format(_selectTraceEvents, traceId),
868
+ null,
869
+ info,
870
+ )
871
+
872
+ while (!trace && attempts++ < _traceMaxAttemps) {
873
+ const sessionResponse =
874
+ await this.controlConnection.query(sessionRequest)
875
+ const sessionRow = sessionResponse.rows[0]
876
+
877
+ if (!sessionRow || typeof sessionRow["duration"] !== "number") {
878
+ await promiseUtils.delay(_traceAttemptDelay)
879
+ continue
880
+ }
881
+
882
+ trace = {
883
+ requestType: sessionRow["request"],
884
+ coordinator: sessionRow["coordinator"],
885
+ parameters: sessionRow["parameters"],
886
+ startedAt: sessionRow["started_at"],
887
+ duration: sessionRow["duration"],
888
+ clientAddress: sessionRow["client"],
889
+ events: null,
890
+ }
891
+
892
+ const eventsResponse =
893
+ await this.controlConnection.query(eventsRequest)
894
+ trace.events = eventsResponse.rows.map((row) => ({
895
+ id: row["event_id"],
896
+ activity: row["activity"],
897
+ source: row["source"],
898
+ elapsed: row["source_elapsed"],
899
+ thread: row["thread"],
900
+ }))
901
+ }
902
+
903
+ if (!trace) {
904
+ throw new Error(
905
+ `Trace ${traceId.toString()} could not fully retrieved after ${_traceMaxAttemps} attempts`,
906
+ )
907
+ }
908
+
909
+ return trace
910
+ }
911
+
912
+ /**
913
+ * Checks whether hosts that are currently up agree on the schema definition.
914
+ * <p>
915
+ * This method performs a one-time check only, without any form of retry; therefore
916
+ * <code>protocolOptions.maxSchemaAgreementWaitSeconds</code> setting does not apply in this case.
917
+ * </p>
918
+ * @param {Function} [callback] A function that is invoked with a value
919
+ * <code>true</code> when all hosts agree on the schema and <code>false</code> when there is no agreement or when
920
+ * the check could not be performed (for example, if the control connection is down).
921
+ * @returns {Promise} Returns a <code>Promise</code> when a callback is not provided. The promise resolves to
922
+ * <code>true</code> when all hosts agree on the schema and <code>false</code> when there is no agreement or when
923
+ * the check could not be performed (for example, if the control connection is down).
924
+ */
925
+ checkSchemaAgreement(callback) {
926
+ return promiseUtils.optionalCallback(
927
+ this._checkSchemaAgreement(),
928
+ callback,
929
+ )
930
+ }
931
+
932
+ /**
933
+ * Async-only version of check schema agreement.
934
+ * @private
935
+ */
936
+ async _checkSchemaAgreement() {
937
+ const connection = this.controlConnection.connection
938
+ if (!connection) {
939
+ return false
940
+ }
941
+ try {
942
+ return await this.compareSchemaVersions(connection)
943
+ } catch (err) {
944
+ return false
945
+ }
946
+ }
947
+
948
+ /**
949
+ * Uses the metadata to fill the user provided parameter hints
950
+ * @param {String} keyspace
951
+ * @param {Array} hints
952
+ * @internal
953
+ * @ignore
954
+ */
955
+ async adaptUserHints(keyspace, hints) {
956
+ if (!Array.isArray(hints)) {
957
+ return
958
+ }
959
+ const udts = []
960
+ // Check for udts and get the metadata
961
+ for (let i = 0; i < hints.length; i++) {
962
+ const hint = hints[i]
963
+ if (typeof hint !== "string") {
964
+ continue
965
+ }
966
+
967
+ const type = types.dataTypes.getByName(hint)
968
+ this._checkUdtTypes(udts, type, keyspace)
969
+ hints[i] = type
970
+ }
971
+
972
+ for (const type of udts) {
973
+ const udtInfo = await this.getUdt(
974
+ type.info.keyspace,
975
+ type.info.name,
976
+ )
977
+ if (!udtInfo) {
978
+ throw new TypeError(
979
+ "User defined type not found: " +
980
+ type.info.keyspace +
981
+ "." +
982
+ type.info.name,
983
+ )
984
+ }
985
+ type.info = udtInfo
986
+ }
987
+ }
988
+
989
+ /**
990
+ * @param {Array} udts
991
+ * @param {{code, info}} type
992
+ * @param {string} keyspace
993
+ * @private
994
+ */
995
+ _checkUdtTypes(udts, type, keyspace) {
996
+ if (type.code === types.dataTypes.udt) {
997
+ const udtName = type.info.split(".")
998
+ type.info = {
999
+ keyspace: udtName[0],
1000
+ name: udtName[1],
1001
+ }
1002
+ if (!type.info.name) {
1003
+ if (!keyspace) {
1004
+ throw new TypeError(
1005
+ "No keyspace specified for udt: " + udtName.join("."),
1006
+ )
1007
+ }
1008
+ //use the provided keyspace
1009
+ type.info.name = type.info.keyspace
1010
+ type.info.keyspace = keyspace
1011
+ }
1012
+ udts.push(type)
1013
+ return
1014
+ }
1015
+
1016
+ if (!type.info) {
1017
+ return
1018
+ }
1019
+ if (
1020
+ type.code === types.dataTypes.list ||
1021
+ type.code === types.dataTypes.set
1022
+ ) {
1023
+ return this._checkUdtTypes(udts, type.info, keyspace)
1024
+ }
1025
+ if (type.code === types.dataTypes.map) {
1026
+ this._checkUdtTypes(udts, type.info[0], keyspace)
1027
+ this._checkUdtTypes(udts, type.info[1], keyspace)
1028
+ }
1029
+ }
1030
+
1031
+ /**
1032
+ * Uses the provided connection to query the schema versions and compare them.
1033
+ * @param {Connection} connection
1034
+ * @internal
1035
+ * @ignore
1036
+ */
1037
+ async compareSchemaVersions(connection) {
1038
+ const versions = new Set()
1039
+ const response1 = await connection.send(
1040
+ new requests.QueryRequest(_selectSchemaVersionLocal),
1041
+ null,
1042
+ )
1043
+ if (response1 && response1.rows && response1.rows.length === 1) {
1044
+ versions.add(response1.rows[0]["schema_version"].toString())
1045
+ }
1046
+ const response2 = await connection.send(
1047
+ new requests.QueryRequest(_selectSchemaVersionPeers),
1048
+ null,
1049
+ )
1050
+ if (response2 && response2.rows) {
1051
+ for (const row of response2.rows) {
1052
+ const value = row["schema_version"]
1053
+ if (!value) {
1054
+ continue
1055
+ }
1056
+ versions.add(value.toString())
1057
+ }
1058
+ }
1059
+ return versions.size === 1
1060
+ }
1061
+ }
1062
+
1063
+ /**
1064
+ * Allows to store prepared queries and retrieval by query or query id.
1065
+ * @ignore
1066
+ */
1067
+ class PreparedQueries {
1068
+ /**
1069
+ * @param {Number} maxPrepared
1070
+ * @param {Function} logger
1071
+ */
1072
+ constructor(maxPrepared, logger) {
1073
+ this.length = 0
1074
+ this._maxPrepared = maxPrepared
1075
+ this._mapByKey = new Map()
1076
+ this._mapById = new Map()
1077
+ this._logger = logger
1078
+ }
1079
+
1080
+ _getKey(keyspace, query) {
1081
+ return (keyspace || "") + query
1082
+ }
1083
+
1084
+ getOrAdd(keyspace, query) {
1085
+ const key = this._getKey(keyspace, query)
1086
+ let info = this._mapByKey.get(key)
1087
+ if (info) {
1088
+ return info
1089
+ }
1090
+
1091
+ this._validateOverflow()
1092
+
1093
+ info = new events.EventEmitter()
1094
+ info.setMaxListeners(0)
1095
+ info.query = query
1096
+ // The keyspace in which it was prepared
1097
+ info.keyspace = keyspace
1098
+ this._mapByKey.set(key, info)
1099
+ this.length++
1100
+ return info
1101
+ }
1102
+
1103
+ _validateOverflow() {
1104
+ if (this.length < this._maxPrepared) {
1105
+ return
1106
+ }
1107
+
1108
+ const toRemove = []
1109
+ this._logger(
1110
+ "warning",
1111
+ "Prepared statements exceeded maximum. This could be caused by preparing queries that contain parameters",
1112
+ )
1113
+
1114
+ const toRemoveLength = this.length - this._maxPrepared + 1
1115
+
1116
+ for (const [key, info] of this._mapByKey) {
1117
+ if (!info.queryId) {
1118
+ // Only remove queries that contain queryId
1119
+ continue
1120
+ }
1121
+
1122
+ const length = toRemove.push([key, info])
1123
+ if (length >= toRemoveLength) {
1124
+ break
1125
+ }
1126
+ }
1127
+
1128
+ for (const [key, info] of toRemove) {
1129
+ this._mapByKey.delete(key)
1130
+ this._mapById.delete(info.queryId.toString("hex"))
1131
+ this.length--
1132
+ }
1133
+ }
1134
+
1135
+ setById(info) {
1136
+ this._mapById.set(info.queryId.toString("hex"), info)
1137
+ }
1138
+
1139
+ getById(id) {
1140
+ return this._mapById.get(id.toString("hex"))
1141
+ }
1142
+
1143
+ clear() {
1144
+ this._mapByKey = new Map()
1145
+ this._mapById = new Map()
1146
+ this.length = 0
1147
+ }
1148
+
1149
+ getAll() {
1150
+ return Array.from(this._mapByKey.values()).filter(
1151
+ (info) => !!info.queryId,
1152
+ )
1153
+ }
1154
+ }
1155
+
1156
+ export default Metadata