@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,225 @@
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 { TableMappings, DefaultTableMappings } from "./table-mappings.js"
20
+
21
+ /**
22
+ * Represents the parsed user information of the table mappings of a model.
23
+ * @ignore
24
+ */
25
+ class ModelMappingInfo {
26
+ /**
27
+ * @param {String} keyspace
28
+ * @param {Array<{name, isView}>} tables
29
+ * @param {TableMappings} mappings
30
+ * @param {Map<String,ModelColumnInfo>} columns
31
+ */
32
+ constructor(keyspace, tables, mappings, columns) {
33
+ this.keyspace = keyspace
34
+ this.tables = tables
35
+ this._mappings = mappings
36
+ this._columns = columns
37
+
38
+ // Define a map of column information per property name
39
+ /** @type {Map<String, ModelColumnInfo>} */
40
+ this._documentProperties = new Map()
41
+ for (const modelColumnInfo of columns.values()) {
42
+ this._documentProperties.set(
43
+ modelColumnInfo.propertyName,
44
+ modelColumnInfo,
45
+ )
46
+ }
47
+ }
48
+
49
+ getColumnName(propName) {
50
+ const modelColumnInfo = this._documentProperties.get(propName)
51
+ if (modelColumnInfo !== undefined) {
52
+ // There is an specific name transformation between the column name and the property name
53
+ return modelColumnInfo.columnName
54
+ }
55
+ // Rely on the TableMappings (i.e. maybe there is a convention defined for this property)
56
+ return this._mappings.getColumnName(propName)
57
+ }
58
+
59
+ getPropertyName(columnName) {
60
+ const modelColumnInfo = this._columns.get(columnName)
61
+ if (modelColumnInfo !== undefined) {
62
+ // There is an specific name transformation between the column name and the property name
63
+ return modelColumnInfo.propertyName
64
+ }
65
+ // Rely on the TableMappings (i.e. maybe there is a convention defined for this column)
66
+ return this._mappings.getPropertyName(columnName)
67
+ }
68
+
69
+ getFromModelFn(propName) {
70
+ const modelColumnInfo = this._documentProperties.get(propName)
71
+ return modelColumnInfo !== undefined ? modelColumnInfo.fromModel : null
72
+ }
73
+
74
+ getToModelFn(columnName) {
75
+ const modelColumnInfo = this._columns.get(columnName)
76
+ return modelColumnInfo !== undefined ? modelColumnInfo.toModel : null
77
+ }
78
+
79
+ newInstance() {
80
+ return this._mappings.newObjectInstance()
81
+ }
82
+
83
+ /**
84
+ * Parses the user options into a map of model names and ModelMappingInfo.
85
+ * @param {MappingOptions} options
86
+ * @param {String} currentKeyspace
87
+ * @returns {Map<String, ModelMappingInfo>}
88
+ */
89
+ static parse(options, currentKeyspace) {
90
+ const result = new Map()
91
+ if (!options || !options.models) {
92
+ return result
93
+ }
94
+
95
+ Object.keys(options.models).forEach((modelName) => {
96
+ const modelOptions = options.models[modelName]
97
+ result.set(
98
+ modelName,
99
+ ModelMappingInfo._create(
100
+ modelName,
101
+ currentKeyspace,
102
+ modelOptions,
103
+ ),
104
+ )
105
+ })
106
+
107
+ return result
108
+ }
109
+
110
+ static _create(modelName, currentKeyspace, modelOptions) {
111
+ if (!currentKeyspace && (!modelOptions || !modelOptions.keyspace)) {
112
+ throw new Error(
113
+ "You should specify the keyspace of the model in the MappingOptions when the Client is not using a keyspace",
114
+ )
115
+ }
116
+
117
+ if (!modelOptions) {
118
+ return ModelMappingInfo.createDefault(modelName, currentKeyspace)
119
+ }
120
+
121
+ let tables
122
+
123
+ if (modelOptions.tables && modelOptions.tables.length > 0) {
124
+ tables = modelOptions.tables.map((item) => {
125
+ const table = { name: null, isView: false }
126
+ if (typeof item === "string") {
127
+ table.name = item
128
+ } else if (item) {
129
+ table.name = item.name
130
+ table.isView = !!item.isView
131
+ }
132
+
133
+ if (!table.name) {
134
+ throw new Error(
135
+ `Table name not specified for model '${modelName}'`,
136
+ )
137
+ }
138
+
139
+ return table
140
+ })
141
+ } else {
142
+ tables = [{ name: modelName, isView: false }]
143
+ }
144
+
145
+ if (
146
+ modelOptions.mappings &&
147
+ !(modelOptions.mappings instanceof TableMappings)
148
+ ) {
149
+ throw new Error("mappings should be an instance of TableMappings")
150
+ }
151
+
152
+ const columns = new Map()
153
+ if (
154
+ modelOptions.columns !== null &&
155
+ typeof modelOptions.columns === "object"
156
+ ) {
157
+ Object.keys(modelOptions.columns).forEach((columnName) => {
158
+ columns.set(
159
+ columnName,
160
+ ModelColumnInfo.parse(
161
+ columnName,
162
+ modelOptions.columns[columnName],
163
+ ),
164
+ )
165
+ })
166
+ }
167
+
168
+ return new ModelMappingInfo(
169
+ modelOptions.keyspace || currentKeyspace,
170
+ tables,
171
+ modelOptions.mappings || new DefaultTableMappings(),
172
+ columns,
173
+ )
174
+ }
175
+
176
+ static createDefault(modelName, currentKeyspace) {
177
+ return new ModelMappingInfo(
178
+ currentKeyspace,
179
+ [{ name: modelName, isView: false }],
180
+ new DefaultTableMappings(),
181
+ new Map(),
182
+ )
183
+ }
184
+ }
185
+
186
+ class ModelColumnInfo {
187
+ constructor(columnName, propertyName, toModel, fromModel) {
188
+ this.columnName = columnName
189
+ this.propertyName = propertyName
190
+
191
+ if (toModel && typeof toModel !== "function") {
192
+ throw new TypeError(
193
+ `toModel type for property '${propertyName}' should be a function (obtained ${typeof toModel})`,
194
+ )
195
+ }
196
+
197
+ if (fromModel && typeof fromModel !== "function") {
198
+ throw new TypeError(
199
+ `fromModel type for property '${propertyName}' should be a function (obtained ${typeof fromModel})`,
200
+ )
201
+ }
202
+
203
+ this.toModel = toModel
204
+ this.fromModel = fromModel
205
+ }
206
+
207
+ static parse(columnName, value) {
208
+ if (!value) {
209
+ return new ModelColumnInfo(columnName, columnName)
210
+ }
211
+
212
+ if (typeof value === "string") {
213
+ return new ModelColumnInfo(columnName, value)
214
+ }
215
+
216
+ return new ModelColumnInfo(
217
+ columnName,
218
+ value.name || columnName,
219
+ value.toModel,
220
+ value.fromModel,
221
+ )
222
+ }
223
+ }
224
+
225
+ export default ModelMappingInfo
@@ -0,0 +1,417 @@
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
+ const keyMatches = {
20
+ all: 1,
21
+ none: 0,
22
+ some: -1,
23
+ }
24
+
25
+ /**
26
+ * Provides utility methods to choose the correct tables and views that should be included in a statement.
27
+ * @ignore
28
+ */
29
+ class ObjectSelector {
30
+ /**
31
+ * Gets the table/view that should be used to execute the SELECT query.
32
+ * @param {Client} client
33
+ * @param {ModelMappingInfo} info
34
+ * @param {Boolean} allPKsDefined
35
+ * @param {Array} propertiesInfo
36
+ * @param {Array} fieldsInfo
37
+ * @param {Array<Array<String>>} orderByColumns
38
+ * @return {Promise<String>} A promise that resolves to a table names.
39
+ */
40
+ static getForSelect(
41
+ client,
42
+ info,
43
+ allPKsDefined,
44
+ propertiesInfo,
45
+ fieldsInfo,
46
+ orderByColumns,
47
+ ) {
48
+ return Promise.all(
49
+ info.tables.map((t) => {
50
+ if (t.isView) {
51
+ return client.metadata.getMaterializedView(
52
+ info.keyspace,
53
+ t.name,
54
+ )
55
+ }
56
+ return client.metadata.getTable(info.keyspace, t.name)
57
+ }),
58
+ ).then((tables) => {
59
+ for (let i = 0; i < tables.length; i++) {
60
+ const table = tables[i]
61
+ if (table === null) {
62
+ throw new Error(
63
+ `Table "${info.tables[i].name}" could not be retrieved`,
64
+ )
65
+ }
66
+
67
+ if (
68
+ keysAreIncluded(table.partitionKeys, propertiesInfo) !==
69
+ keyMatches.all
70
+ ) {
71
+ // Not all the partition keys are covered
72
+ continue
73
+ }
74
+
75
+ if (allPKsDefined) {
76
+ if (
77
+ keysAreIncluded(
78
+ table.clusteringKeys,
79
+ propertiesInfo,
80
+ ) !== keyMatches.all
81
+ ) {
82
+ // All clustering keys should be included as allPKsDefined flag is set
83
+ continue
84
+ }
85
+ }
86
+
87
+ if (propertiesInfo.length > table.partitionKeys.length) {
88
+ // Check that the Where clause is composed by partition and clustering keys
89
+ const allPropertiesArePrimaryKeys = propertiesInfo.reduce(
90
+ (acc, p) =>
91
+ acc &&
92
+ (contains(
93
+ table.partitionKeys,
94
+ (c) => c.name === p.columnName,
95
+ ) ||
96
+ contains(
97
+ table.clusteringKeys,
98
+ (c) => c.name === p.columnName,
99
+ )),
100
+ true,
101
+ )
102
+
103
+ if (!allPropertiesArePrimaryKeys) {
104
+ continue
105
+ }
106
+ }
107
+
108
+ // All fields must be contained
109
+ const containsAllFields = fieldsInfo.reduce(
110
+ (acc, p) =>
111
+ acc && table.columnsByName[p.columnName] !== undefined,
112
+ true,
113
+ )
114
+
115
+ if (!containsAllFields) {
116
+ continue
117
+ }
118
+
119
+ // CQL:
120
+ // - "ORDER BY" is currently only supported on the clustered columns of the PRIMARY KEY
121
+ // - "ORDER BY" currently only support the ordering of columns following their declared order in
122
+ // the PRIMARY KEY
123
+ //
124
+ // In the mapper, we validate that the ORDER BY columns appear in the same order as in the clustering keys
125
+ const containsAllOrderByColumns = orderByColumns.reduce(
126
+ (acc, order, index) => {
127
+ if (!acc) {
128
+ return false
129
+ }
130
+
131
+ const ck = table.clusteringKeys[index]
132
+
133
+ return ck && ck.name === order[0]
134
+ },
135
+ true,
136
+ )
137
+
138
+ if (!containsAllOrderByColumns) {
139
+ continue
140
+ }
141
+
142
+ return table.name
143
+ }
144
+
145
+ let message = `No table matches the filter (${allPKsDefined ? "all PKs have to be specified" : "PKs"}): [${propertiesInfo.map(
146
+ (p) => p.columnName,
147
+ )}]`
148
+
149
+ if (fieldsInfo.length > 0) {
150
+ message += `; fields: [${fieldsInfo.map((p) => p.columnName)}]`
151
+ }
152
+ if (orderByColumns.length > 0) {
153
+ message += `; orderBy: [${orderByColumns.map((item) => item[0])}]`
154
+ }
155
+
156
+ throw new Error(message)
157
+ })
158
+ }
159
+
160
+ /** Returns the name of the first table */
161
+ static getForSelectAll(info) {
162
+ return info.tables[0].name
163
+ }
164
+
165
+ /**
166
+ * Gets the tables that should be used to execute the INSERT query.
167
+ * @param {Client} client
168
+ * @param {ModelMappingInfo} info
169
+ * @param {Array} propertiesInfo
170
+ * @return {Promise<Array<TableMetadata>>} A promise that resolves to an Array of tables.
171
+ */
172
+ static getForInsert(client, info, propertiesInfo) {
173
+ return Promise.all(
174
+ info.tables
175
+ .filter((t) => !t.isView)
176
+ .map((t) => client.metadata.getTable(info.keyspace, t.name)),
177
+ ).then((tables) => {
178
+ const filteredTables = tables.filter((table, i) => {
179
+ if (table === null) {
180
+ throw new Error(
181
+ `Table "${info.tables[i].name}" could not be retrieved`,
182
+ )
183
+ }
184
+
185
+ if (
186
+ keysAreIncluded(table.partitionKeys, propertiesInfo) !==
187
+ keyMatches.all
188
+ ) {
189
+ // Not all the partition keys are covered
190
+ return false
191
+ }
192
+
193
+ const clusteringKeyMatches = keysAreIncluded(
194
+ table.clusteringKeys,
195
+ propertiesInfo,
196
+ )
197
+
198
+ // All clustering keys should be included or it can be inserting a static column value
199
+ if (clusteringKeyMatches === keyMatches.all) {
200
+ return true
201
+ }
202
+
203
+ if (clusteringKeyMatches === keyMatches.some) {
204
+ return false
205
+ }
206
+
207
+ const staticColumns = staticColumnCount(table)
208
+ return (
209
+ propertiesInfo.length ===
210
+ table.partitionKeys.length + staticColumns &&
211
+ staticColumns > 0
212
+ )
213
+ })
214
+
215
+ if (filteredTables.length === 0) {
216
+ throw new Error(
217
+ `No table matches (all PKs have to be specified) fields: [${propertiesInfo.map(
218
+ (p) => p.columnName,
219
+ )}]`,
220
+ )
221
+ }
222
+
223
+ return filteredTables
224
+ })
225
+ }
226
+
227
+ /**
228
+ * Gets the tables that should be used to execute the UPDATE query.
229
+ * @param {Client} client
230
+ * @param {ModelMappingInfo} info
231
+ * @param {Array} propertiesInfo
232
+ * @param {Array} when
233
+ * @return {Promise<Array<TableMetadata>>} A promise that resolves to an Array of tables.
234
+ */
235
+ static getForUpdate(client, info, propertiesInfo, when) {
236
+ return Promise.all(
237
+ info.tables
238
+ .filter((t) => !t.isView)
239
+ .map((t) => client.metadata.getTable(info.keyspace, t.name)),
240
+ ).then((tables) => {
241
+ const filteredTables = tables.filter((table, i) => {
242
+ if (table === null) {
243
+ throw new Error(
244
+ `Table "${info.tables[i].name}" could not be retrieved`,
245
+ )
246
+ }
247
+
248
+ if (
249
+ keysAreIncluded(table.partitionKeys, propertiesInfo) !==
250
+ keyMatches.all
251
+ ) {
252
+ // Not all the partition keys are covered
253
+ return false
254
+ }
255
+
256
+ const clusteringKeyMatches = keysAreIncluded(
257
+ table.clusteringKeys,
258
+ propertiesInfo,
259
+ )
260
+
261
+ // All clustering keys should be included or it can be updating a static column value
262
+ if (clusteringKeyMatches === keyMatches.some) {
263
+ return false
264
+ }
265
+
266
+ if (
267
+ clusteringKeyMatches === keyMatches.none &&
268
+ !hasStaticColumn(table)
269
+ ) {
270
+ return false
271
+ }
272
+
273
+ const applicableColumns = propertiesInfo.reduce(
274
+ (acc, p) =>
275
+ acc +
276
+ (table.columnsByName[p.columnName] !== undefined
277
+ ? 1
278
+ : 0),
279
+ 0,
280
+ )
281
+
282
+ if (
283
+ applicableColumns <=
284
+ table.partitionKeys.length + table.clusteringKeys.length
285
+ ) {
286
+ if (
287
+ !hasStaticColumn(table) ||
288
+ applicableColumns <= table.partitionKeys.length
289
+ ) {
290
+ // UPDATE statement does not contain columns to SET
291
+ return false
292
+ }
293
+ }
294
+
295
+ // "when" conditions should be contained in the table
296
+ return when.reduce(
297
+ (acc, p) =>
298
+ acc && table.columnsByName[p.columnName] !== undefined,
299
+ true,
300
+ )
301
+ })
302
+
303
+ if (filteredTables.length === 0) {
304
+ let message = `No table matches (all PKs and columns to set have to be specified) fields: [${propertiesInfo.map(
305
+ (p) => p.columnName,
306
+ )}]`
307
+
308
+ if (when.length > 0) {
309
+ message += `; condition: [${when.map((p) => p.columnName)}]`
310
+ }
311
+
312
+ throw new Error(message)
313
+ }
314
+
315
+ return filteredTables
316
+ })
317
+ }
318
+
319
+ /**
320
+ * Gets the tables that should be used to execute the DELETE query.
321
+ * @param {Client} client
322
+ * @param {ModelMappingInfo} info
323
+ * @param {Array} propertiesInfo
324
+ * @param {Array} when
325
+ * @return {Promise<Array<TableMetadata>>} A promise that resolves to an Array of tables.
326
+ */
327
+ static getForDelete(client, info, propertiesInfo, when) {
328
+ return Promise.all(
329
+ info.tables
330
+ .filter((t) => !t.isView)
331
+ .map((t) => client.metadata.getTable(info.keyspace, t.name)),
332
+ ).then((tables) => {
333
+ const filteredTables = tables.filter((table, i) => {
334
+ if (table === null) {
335
+ throw new Error(
336
+ `Table "${info.tables[i].name}" could not be retrieved`,
337
+ )
338
+ }
339
+
340
+ // All partition and clustering keys from the table should be included in the document
341
+ const keyNames = table.partitionKeys
342
+ .concat(table.clusteringKeys)
343
+ .map((k) => k.name)
344
+ const columns = propertiesInfo.map((p) => p.columnName)
345
+
346
+ for (let i = 0; i < keyNames.length; i++) {
347
+ if (columns.indexOf(keyNames[i]) === -1) {
348
+ return false
349
+ }
350
+ }
351
+
352
+ // "when" conditions should be contained in the table
353
+ return when.reduce(
354
+ (acc, p) =>
355
+ acc && table.columnsByName[p.columnName] !== undefined,
356
+ true,
357
+ )
358
+ })
359
+
360
+ if (filteredTables.length === 0) {
361
+ let message = `No table matches (all PKs have to be specified) fields: [${propertiesInfo.map(
362
+ (p) => p.columnName,
363
+ )}]`
364
+
365
+ if (when.length > 0) {
366
+ message += `; condition: [${when.map((p) => p.columnName)}]`
367
+ }
368
+
369
+ throw new Error(message)
370
+ }
371
+
372
+ return filteredTables
373
+ })
374
+ }
375
+ }
376
+
377
+ function contains(arr, fn) {
378
+ return arr.filter(fn).length > 0
379
+ }
380
+
381
+ /**
382
+ * Returns the amount of matches for a given key
383
+ * @private
384
+ * @param {Array} keys
385
+ * @param {Array} propertiesInfo
386
+ */
387
+ function keysAreIncluded(keys, propertiesInfo) {
388
+ if (keys.length === 0) {
389
+ return keyMatches.all
390
+ }
391
+
392
+ // Filtering by name might look slow / ineffective to using hash maps
393
+ // but we expect `keys` and `propertiesInfo` to contain only few items
394
+ const matches = propertiesInfo.reduce(
395
+ (acc, p) =>
396
+ acc + (contains(keys, (k) => p.columnName === k.name) ? 1 : 0),
397
+ 0,
398
+ )
399
+ if (matches === 0) {
400
+ return keyMatches.none
401
+ }
402
+
403
+ return matches === keys.length ? keyMatches.all : keyMatches.some
404
+ }
405
+
406
+ function hasStaticColumn(table) {
407
+ return staticColumnCount(table) > 0
408
+ }
409
+
410
+ function staticColumnCount(table) {
411
+ return table.columns.reduce(
412
+ (acc, column) => acc + (column.isStatic ? 1 : 0),
413
+ 0,
414
+ )
415
+ }
416
+
417
+ export default ObjectSelector