spice-couchbase 2.0.52 → 2.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.js +12 -7
- package/build/utility.js +23 -9
- package/package.json +1 -1
- package/src/index.js +19 -10
- package/src/utility.js +25 -8
package/build/index.js
CHANGED
|
@@ -177,6 +177,13 @@ module.exports = class Database {
|
|
|
177
177
|
return "default:".concat(this.quoteIdentifier(this.db_config.bucket), ".").concat(this.quoteIdentifier(scope_name), ".").concat(this.quoteIdentifier(collection_name));
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
isCollectionAlreadyExistsError(error) {
|
|
181
|
+
var _error$cause, _error$context, _error$context2, _error$cause2, _error$cause3;
|
|
182
|
+
|
|
183
|
+
var message = (error === null || error === void 0 ? void 0 : error.message) || (error === null || error === void 0 ? void 0 : (_error$cause = error.cause) === null || _error$cause === void 0 ? void 0 : _error$cause.first_error_message) || (error === null || error === void 0 ? void 0 : (_error$context = error.context) === null || _error$context === void 0 ? void 0 : _error$context.first_error_message) || (error === null || error === void 0 ? void 0 : (_error$context2 = error.context) === null || _error$context2 === void 0 ? void 0 : _error$context2.http_response_body) || "";
|
|
184
|
+
return error instanceof couchbase.CollectionExistsError || (error === null || error === void 0 ? void 0 : (_error$cause2 = error.cause) === null || _error$cause2 === void 0 ? void 0 : _error$cause2.first_error_code) === 12003 || (error === null || error === void 0 ? void 0 : (_error$cause3 = error.cause) === null || _error$cause3 === void 0 ? void 0 : _error$cause3.first_error_code) === 12027 || /collection .*already exists|already exists/i.test(message);
|
|
185
|
+
}
|
|
186
|
+
|
|
180
187
|
upsertCollection() {
|
|
181
188
|
var _this3 = this;
|
|
182
189
|
|
|
@@ -207,7 +214,7 @@ module.exports = class Database {
|
|
|
207
214
|
if ((_spice2 = spice) === null || _spice2 === void 0 ? void 0 : (_spice2$db = _spice2.db) === null || _spice2$db === void 0 ? void 0 : (_spice2$db$collection = _spice2$db.collections) === null || _spice2$db$collection === void 0 ? void 0 : _spice2$db$collection.has(collection_name)) {
|
|
208
215
|
return true;
|
|
209
216
|
} else {
|
|
210
|
-
var response = yield _this4.cluster.query("SELECT RAW
|
|
217
|
+
var response = yield _this4.cluster.query("SELECT RAW keyspace_id\n FROM system:keyspaces\n WHERE bucket_id = ?\n AND scope_id = ?\n AND keyspace_id = ?\n LIMIT 1", {
|
|
211
218
|
parameters: [_this4.db_config.bucket, scope_name, collection_name],
|
|
212
219
|
scanConsistency: QueryScanConsistency.RequestPlus
|
|
213
220
|
});
|
|
@@ -237,9 +244,7 @@ module.exports = class Database {
|
|
|
237
244
|
spice.db.collections.add(connection_name);
|
|
238
245
|
return response;
|
|
239
246
|
} catch (e) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (e instanceof couchbase.CollectionExistsError || (e === null || e === void 0 ? void 0 : (_e$cause = e.cause) === null || _e$cause === void 0 ? void 0 : _e$cause.first_error_code) === 12003 || /collection .*already exists|already exists/i.test((e === null || e === void 0 ? void 0 : e.message) || (e === null || e === void 0 ? void 0 : (_e$cause2 = e.cause) === null || _e$cause2 === void 0 ? void 0 : _e$cause2.first_error_message) || "")) {
|
|
247
|
+
if (_this5.isCollectionAlreadyExistsError(e)) {
|
|
243
248
|
spice.db.collections.add(connection_name);
|
|
244
249
|
return;
|
|
245
250
|
}
|
|
@@ -278,9 +283,9 @@ module.exports = class Database {
|
|
|
278
283
|
var response = yield _this7.query("CREATE PRIMARY INDEX ON ".concat(_this7.collectionPath(scope_name, col_name)));
|
|
279
284
|
return response;
|
|
280
285
|
} catch (e) {
|
|
281
|
-
var _e$
|
|
286
|
+
var _e$cause, _e$cause2;
|
|
282
287
|
|
|
283
|
-
if (e instanceof couchbase.IndexExistsError || (e === null || e === void 0 ? void 0 : (_e$
|
|
288
|
+
if (e instanceof couchbase.IndexExistsError || (e === null || e === void 0 ? void 0 : (_e$cause = e.cause) === null || _e$cause === void 0 ? void 0 : _e$cause.first_error_code) === 4300 || /index .*already exists|index_exists/i.test((e === null || e === void 0 ? void 0 : e.message) || (e === null || e === void 0 ? void 0 : (_e$cause2 = e.cause) === null || _e$cause2 === void 0 ? void 0 : _e$cause2.first_error_message) || "")) {
|
|
284
289
|
return;
|
|
285
290
|
}
|
|
286
291
|
|
|
@@ -865,7 +870,7 @@ module.exports = class Database {
|
|
|
865
870
|
|
|
866
871
|
var durationInMilliseconds = (endTime - startTime) / BigInt(1000000); // optimize if response time is greater than 1 second
|
|
867
872
|
|
|
868
|
-
if (durationInMilliseconds >
|
|
873
|
+
if (durationInMilliseconds > 1000) {
|
|
869
874
|
_this20.optimize(query, exec_scope);
|
|
870
875
|
}
|
|
871
876
|
|
package/build/utility.js
CHANGED
|
@@ -284,8 +284,21 @@ function splitSelectClause(clause) {
|
|
|
284
284
|
|
|
285
285
|
return parts;
|
|
286
286
|
}
|
|
287
|
+
|
|
288
|
+
function hasQualifiedWildcardProjection(query) {
|
|
289
|
+
var selectMatch = query.match(/\bSELECT\b([\s\S]+?)\bFROM\b/i);
|
|
290
|
+
|
|
291
|
+
if (!selectMatch) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return splitSelectClause(selectMatch[1].trim()).some(part => {
|
|
296
|
+
var trimmedPart = part.trim();
|
|
297
|
+
return /^\(\s*`?\w+`?\s*\)\s*\.\s*\*$/.test(trimmedPart) || /^`?\w+`?\s*\.\s*\*$/.test(trimmedPart);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
287
300
|
/**
|
|
288
|
-
* Builds a covering index CREATE statement from a query
|
|
301
|
+
* Builds a covering or fallback non-covering index CREATE statement from a query
|
|
289
302
|
* @param {string} query - The N1QL query to analyze
|
|
290
303
|
* @param {Object} options - Configuration options
|
|
291
304
|
* @param {string} options.bucket - The bucket name
|
|
@@ -298,19 +311,19 @@ function splitSelectClause(clause) {
|
|
|
298
311
|
function buildCoveringIndex(query, options) {
|
|
299
312
|
var {
|
|
300
313
|
bucket,
|
|
301
|
-
scope,
|
|
302
|
-
collection
|
|
314
|
+
scope = "_default",
|
|
315
|
+
collection = "_default"
|
|
303
316
|
} = options;
|
|
304
317
|
|
|
305
|
-
if (!bucket
|
|
318
|
+
if (!bucket) {
|
|
306
319
|
return null;
|
|
307
320
|
} // Extract fields from SELECT clause
|
|
308
321
|
|
|
309
322
|
|
|
310
323
|
var selectFields = extractSelectFields(query);
|
|
324
|
+
var isNonCoveringFallback = selectFields === null && hasQualifiedWildcardProjection(query);
|
|
311
325
|
|
|
312
|
-
if (selectFields === null) {
|
|
313
|
-
// SELECT * query - cannot create covering index
|
|
326
|
+
if (selectFields === null && !isNonCoveringFallback) {
|
|
314
327
|
return null;
|
|
315
328
|
} // Extract fields from WHERE clause
|
|
316
329
|
|
|
@@ -349,7 +362,7 @@ function buildCoveringIndex(query, options) {
|
|
|
349
362
|
return true;
|
|
350
363
|
}); // Filter SELECT fields (remove duplicates from WHERE, GROUP BY, and ORDER BY, remove deleted)
|
|
351
364
|
|
|
352
|
-
var selectFieldsFiltered = selectFields.filter(f => {
|
|
365
|
+
var selectFieldsFiltered = (selectFields || []).filter(f => {
|
|
353
366
|
var lower = f.toLowerCase();
|
|
354
367
|
|
|
355
368
|
if (addedFields.has(lower) || lower === "deleted") {
|
|
@@ -373,9 +386,10 @@ function buildCoveringIndex(query, options) {
|
|
|
373
386
|
|
|
374
387
|
var indexFieldsKey = allFields.map(field => formatIndexKey(field, orderDirectionMap)).sort().join(",");
|
|
375
388
|
var orderFieldsKey = orderByIndexFields.map(field => formatIndexKey(field.name, orderDirectionMap)).join(",");
|
|
376
|
-
var
|
|
389
|
+
var indexMode = isNonCoveringFallback ? "noncovered" : "covered";
|
|
390
|
+
var indexKey = "".concat(bucket, "|").concat(scope, "|").concat(collection, "|mode:").concat(indexMode, "|fields:").concat(indexFieldsKey, "|order:").concat(orderFieldsKey);
|
|
377
391
|
var hash = generateHash(indexKey);
|
|
378
|
-
var indexName = "
|
|
392
|
+
var indexName = "adv_".concat(indexMode, "_").concat(hash); // Build the CREATE INDEX statement
|
|
379
393
|
|
|
380
394
|
var indexStatement = "CREATE INDEX `".concat(indexName, "` ON `default`:`").concat(bucket, "`.`").concat(scope, "`.`").concat(collection, "`(").concat(fieldList, ")");
|
|
381
395
|
return indexStatement;
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -176,6 +176,21 @@ module.exports = class Database {
|
|
|
176
176
|
)}.${this.quoteIdentifier(collection_name)}`;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
isCollectionAlreadyExistsError(error) {
|
|
180
|
+
const message =
|
|
181
|
+
error?.message ||
|
|
182
|
+
error?.cause?.first_error_message ||
|
|
183
|
+
error?.context?.first_error_message ||
|
|
184
|
+
error?.context?.http_response_body ||
|
|
185
|
+
"";
|
|
186
|
+
return (
|
|
187
|
+
error instanceof couchbase.CollectionExistsError ||
|
|
188
|
+
error?.cause?.first_error_code === 12003 ||
|
|
189
|
+
error?.cause?.first_error_code === 12027 ||
|
|
190
|
+
/collection .*already exists|already exists/i.test(message)
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
179
194
|
async upsertCollection() {
|
|
180
195
|
try {
|
|
181
196
|
if (!this.checkGlobelConnectionObject(this.collection_name)) {
|
|
@@ -200,11 +215,11 @@ module.exports = class Database {
|
|
|
200
215
|
return true;
|
|
201
216
|
} else {
|
|
202
217
|
let response = await this.cluster.query(
|
|
203
|
-
`SELECT RAW
|
|
218
|
+
`SELECT RAW keyspace_id
|
|
204
219
|
FROM system:keyspaces
|
|
205
220
|
WHERE bucket_id = ?
|
|
206
221
|
AND scope_id = ?
|
|
207
|
-
AND
|
|
222
|
+
AND keyspace_id = ?
|
|
208
223
|
LIMIT 1`,
|
|
209
224
|
{
|
|
210
225
|
parameters: [this.db_config.bucket, scope_name, collection_name],
|
|
@@ -234,13 +249,7 @@ module.exports = class Database {
|
|
|
234
249
|
spice.db.collections.add(connection_name);
|
|
235
250
|
return response;
|
|
236
251
|
} catch (e) {
|
|
237
|
-
if (
|
|
238
|
-
e instanceof couchbase.CollectionExistsError ||
|
|
239
|
-
e?.cause?.first_error_code === 12003 ||
|
|
240
|
-
/collection .*already exists|already exists/i.test(
|
|
241
|
-
e?.message || e?.cause?.first_error_message || "",
|
|
242
|
-
)
|
|
243
|
-
) {
|
|
252
|
+
if (this.isCollectionAlreadyExistsError(e)) {
|
|
244
253
|
spice.db.collections.add(connection_name);
|
|
245
254
|
return;
|
|
246
255
|
}
|
|
@@ -784,7 +793,7 @@ module.exports = class Database {
|
|
|
784
793
|
// Calculate duration in microseconds
|
|
785
794
|
let durationInMilliseconds = (endTime - startTime) / BigInt(1000000);
|
|
786
795
|
// optimize if response time is greater than 1 second
|
|
787
|
-
if (durationInMilliseconds >
|
|
796
|
+
if (durationInMilliseconds > 1000) {
|
|
788
797
|
this.optimize(query, exec_scope);
|
|
789
798
|
}
|
|
790
799
|
return response;
|
package/src/utility.js
CHANGED
|
@@ -293,8 +293,23 @@ function splitSelectClause(clause) {
|
|
|
293
293
|
return parts;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
function hasQualifiedWildcardProjection(query) {
|
|
297
|
+
const selectMatch = query.match(/\bSELECT\b(.+?)\bFROM\b/is);
|
|
298
|
+
if (!selectMatch) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return splitSelectClause(selectMatch[1].trim()).some((part) => {
|
|
303
|
+
const trimmedPart = part.trim();
|
|
304
|
+
return (
|
|
305
|
+
/^\(\s*`?\w+`?\s*\)\s*\.\s*\*$/.test(trimmedPart) ||
|
|
306
|
+
/^`?\w+`?\s*\.\s*\*$/.test(trimmedPart)
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
296
311
|
/**
|
|
297
|
-
* Builds a covering index CREATE statement from a query
|
|
312
|
+
* Builds a covering or fallback non-covering index CREATE statement from a query
|
|
298
313
|
* @param {string} query - The N1QL query to analyze
|
|
299
314
|
* @param {Object} options - Configuration options
|
|
300
315
|
* @param {string} options.bucket - The bucket name
|
|
@@ -303,16 +318,17 @@ function splitSelectClause(clause) {
|
|
|
303
318
|
* @returns {string|null} - The CREATE INDEX statement, or null if cannot build
|
|
304
319
|
*/
|
|
305
320
|
function buildCoveringIndex(query, options) {
|
|
306
|
-
const { bucket, scope, collection } = options;
|
|
321
|
+
const { bucket, scope = "_default", collection = "_default" } = options;
|
|
307
322
|
|
|
308
|
-
if (!bucket
|
|
323
|
+
if (!bucket) {
|
|
309
324
|
return null;
|
|
310
325
|
}
|
|
311
326
|
|
|
312
327
|
// Extract fields from SELECT clause
|
|
313
328
|
const selectFields = extractSelectFields(query);
|
|
314
|
-
|
|
315
|
-
|
|
329
|
+
const isNonCoveringFallback =
|
|
330
|
+
selectFields === null && hasQualifiedWildcardProjection(query);
|
|
331
|
+
if (selectFields === null && !isNonCoveringFallback) {
|
|
316
332
|
return null;
|
|
317
333
|
}
|
|
318
334
|
|
|
@@ -356,7 +372,7 @@ function buildCoveringIndex(query, options) {
|
|
|
356
372
|
});
|
|
357
373
|
|
|
358
374
|
// Filter SELECT fields (remove duplicates from WHERE, GROUP BY, and ORDER BY, remove deleted)
|
|
359
|
-
const selectFieldsFiltered = selectFields.filter((f) => {
|
|
375
|
+
const selectFieldsFiltered = (selectFields || []).filter((f) => {
|
|
360
376
|
const lower = f.toLowerCase();
|
|
361
377
|
if (addedFields.has(lower) || lower === "deleted") {
|
|
362
378
|
return false;
|
|
@@ -393,9 +409,10 @@ function buildCoveringIndex(query, options) {
|
|
|
393
409
|
const orderFieldsKey = orderByIndexFields
|
|
394
410
|
.map((field) => formatIndexKey(field.name, orderDirectionMap))
|
|
395
411
|
.join(",");
|
|
396
|
-
const
|
|
412
|
+
const indexMode = isNonCoveringFallback ? "noncovered" : "covered";
|
|
413
|
+
const indexKey = `${bucket}|${scope}|${collection}|mode:${indexMode}|fields:${indexFieldsKey}|order:${orderFieldsKey}`;
|
|
397
414
|
const hash = generateHash(indexKey);
|
|
398
|
-
const indexName = `
|
|
415
|
+
const indexName = `adv_${indexMode}_${hash}`;
|
|
399
416
|
|
|
400
417
|
// Build the CREATE INDEX statement
|
|
401
418
|
const indexStatement = `CREATE INDEX \`${indexName}\` ON \`default\`:\`${bucket}\`.\`${scope}\`.\`${collection}\`(${fieldList})`;
|