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 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 name\n FROM system:keyspaces\n WHERE bucket_id = ?\n AND scope_id = ?\n AND name = ?\n LIMIT 1", {
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
- var _e$cause, _e$cause2;
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$cause3, _e$cause4;
286
+ var _e$cause, _e$cause2;
282
287
 
283
- if (e instanceof couchbase.IndexExistsError || (e === null || e === void 0 ? void 0 : (_e$cause3 = e.cause) === null || _e$cause3 === void 0 ? void 0 : _e$cause3.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$cause4 = e.cause) === null || _e$cause4 === void 0 ? void 0 : _e$cause4.first_error_message) || "")) {
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 > 2000) {
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 || !scope || !collection) {
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 indexKey = "".concat(bucket, "|").concat(scope, "|").concat(collection, "|fields:").concat(indexFieldsKey, "|order:").concat(orderFieldsKey);
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 = "adv_covered_".concat(hash); // Build the CREATE INDEX statement
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spice-couchbase",
3
- "version": "2.0.52",
3
+ "version": "2.0.54",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
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 name
218
+ `SELECT RAW keyspace_id
204
219
  FROM system:keyspaces
205
220
  WHERE bucket_id = ?
206
221
  AND scope_id = ?
207
- AND name = ?
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 > 2000) {
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 || !scope || !collection) {
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
- if (selectFields === null) {
315
- // SELECT * query - cannot create covering index
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 indexKey = `${bucket}|${scope}|${collection}|fields:${indexFieldsKey}|order:${orderFieldsKey}`;
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 = `adv_covered_${hash}`;
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})`;