diff --git a/src/commands/documents/filterDocuments.ts b/src/commands/documents/filterDocuments.ts index 0e623ed6..da1e156b 100644 --- a/src/commands/documents/filterDocuments.ts +++ b/src/commands/documents/filterDocuments.ts @@ -3,26 +3,26 @@ import * as vscode from "vscode"; import { Memory } from "../../util/util"; import { IFilterDocuments } from "../../types/IFilterDocuments"; import { logger } from "../../logger/logger"; -import { ParsingFailureError } from "couchbase"; +import { ParsingFailureError, PlanningFailureError } from "couchbase"; export const filterDocuments = async (node: CollectionNode) => { // Check if indexes are present for collection const query = ` SELECT COUNT(*) AS indexCount FROM system:indexes - WHERE bucket_id="${node.bucketName}" AND scope_id="${node.scopeName}" AND keyspace_id="${node.collectionName}" AND is_primary=true + WHERE bucket_id="${node.bucketName}" AND scope_id="${node.scopeName}" AND keyspace_id="${node.collectionName}" `; // Execute the query - const primaryIndexExists = await node.connection.cluster + const indexExists = await node.connection.cluster ?.query(query) .then((result) => { const rows = result.rows; if (!(rows.length > 0 && rows[0].indexCount > 0)) { - // Primary Index Doesn't Exists + // Index Doesn't Exists vscode.window.showErrorMessage( - "Primary index doesn't exists for this document, Please create one before setting document filter" + "Filters can only be applied to collections that have at least one index." ); logger.error( - "Error setting document filter: Primary index doesn't exists" + "Error setting document filter: index doesn't exist" ); return false; } @@ -32,7 +32,7 @@ export const filterDocuments = async (node: CollectionNode) => { logger.error("Error checking primary index: " + err); return false; }); - if (!primaryIndexExists) { + if (!indexExists) { return; } @@ -74,7 +74,13 @@ export const filterDocuments = async (node: CollectionNode) => { vscode.window.showErrorMessage( "Parsing Failed: Incorrect filter definition" ); - } else { + } + else if (err instanceof PlanningFailureError) { + vscode.window.showErrorMessage( + "Planning Failed: Incorrect filter definition, check if the query is correct" + ); + } + else { logger.error(err); } return; diff --git a/src/model/CollectionNode.ts b/src/model/CollectionNode.ts index cefd73e4..466ac2b7 100644 --- a/src/model/CollectionNode.ts +++ b/src/model/CollectionNode.ts @@ -54,6 +54,46 @@ export default class CollectionNode implements INode { ); } + public async getIndexedField(): Promise { + const idxs = await this.connection?.cluster?.queryIndexes().getAllIndexes(this.bucketName, { scopeName: this.scopeName, collectionName: this.collectionName }); + let filter: string | null = null; + if (!idxs) { + return null; + } + + for (const idx of idxs) { + if (idx.isPrimary) { + return "meta().id"; + } else { + const result = this.getValidIndexKey(idx.indexKey); + if (result) { + if (!idx.condition && result[1]) { + return result[0]; + } else { + filter = result[0]; + } + } + } + } + + return filter; + } + + private getValidIndexKey(array: any[]): [string, boolean] | null { + for (let i = 0; i < array.length; i++) { + let key: string = array[i]; + if (!key.includes("(")) { + if (key.endsWith(" DESC") || key.endsWith(" ASC")) { + key = key.replace(" ASC", "").replace(" DESC", "").trim(); + } + key = key.replace(/`/g, ""); + + return [key, i === 0]; + } + } + return null; + } + public async getTreeItem(): Promise { return { label: `${this.collectionName} (${abbreviateCount(this.documentCount)})`, @@ -117,8 +157,8 @@ export default class CollectionNode implements INode { } // TODO: default limit could be managed as user settings / preference let result; - // A primary index is required for database querying. If one is present, a result will be obtained. - // If not, the user will be prompted to create a primary index before querying. + // An index is required for database querying. If one is present, a result will be obtained. + // If not, the user will be prompted to create a index before querying. let docFilter = Memory.state.get( `filterDocuments-${this.connection.connectionIdentifier}-${this.bucketName}-${this.scopeName}-${this.collectionName}` ); @@ -133,9 +173,13 @@ export default class CollectionNode implements INode { } else { try { + // selects the attribute that needs to be used according to the index + const idxField = await this.getIndexedField(); + if (idxField === null) { + throw new PlanningFailureError(); + } result = await connection?.cluster?.query( - `SELECT RAW META().id FROM \`${this.bucketName}\`.\`${this.scopeName - }\`.\`${this.collectionName}\` ${filter.length > 0 ? "WHERE " + filter : "" + `SELECT RAW META().id FROM \`${this.bucketName}\`.\`${this.scopeName}\`.\`${this.collectionName}\` ${'WHERE ' + idxField + ' IS NOT MISSING'} ${filter.length > 0 ? "AND " + filter : "" } LIMIT ${this.limit}` ); } catch (err) {