Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into DA#241-CLI-Dependency…
Browse files Browse the repository at this point in the history
…-Downloader
  • Loading branch information
lokesh-couchbase committed Oct 10, 2023
2 parents 10a5f67 + c6391f6 commit 54a891f
Show file tree
Hide file tree
Showing 19 changed files with 275 additions and 254 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Implement Favourite Query in workbench
- Add document filter
- Add Cluster Overview Window
- Move Indexes inside Collection Directory

## [v0.6.4]
- Create Query Notebook
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This extension is designed to provide a seamless experience for Couchbase Server
This extension requires OpenSSL to be installed on your system in order to install successfully.

For Windows:
- Download the latest version of OpenSSL from https://slproweb.com/products/Win32OpenSSL.html and follow the installation instructions.
- Download the version 1.1.x of OpenSSL from https://slproweb.com/products/Win32OpenSSL.html and follow the installation instructions.

For Debian or Ubuntu-based distros::
- Run `sudo apt-get install openssl`
Expand Down Expand Up @@ -68,5 +68,12 @@ Click on Scope to list Collections and Indexes. Open context menu on Collection

<img src="gifs/QueryNotebook.gif" height="80%" width="80%" alt="Interact with Documents" />

### SQL++ Workbench
1. Fully functional workbench to run SQL++ query and see result in Tabular, JSON and Explain Plan format.
2. Query History to see last executed query.
3. Favourite your query and set Query context.

<img src="gifs/workbench.gif" height="80%" width="80%" alt="Interact with Documents" />

## License
Apache Software License Version 2. See individual files for details.
Binary file added gifs/workbench.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@
},
{
"command": "vscode-couchbase.createCollection",
"when": "view == couchbase && viewItem == collectionDirectory"
"when": "view == couchbase && viewItem == scope"
},
{
"command": "vscode-couchbase.removeCollection",
Expand All @@ -860,7 +860,7 @@
},
{
"command": "vscode-couchbase.refreshCollections",
"when": "view == couchbase && viewItem == collectionDirectory"
"when": "view == couchbase && viewItem == scope"
},
{
"command": "vscode-couchbase.refreshIndexes",
Expand Down
24 changes: 17 additions & 7 deletions src/commands/collections/createCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import * as vscode from "vscode";
import { IConnection } from "../../types/IConnection";
import { Memory } from "../../util/util";
import { logger } from "../../logger/logger";
import { CollectionDirectory } from "../../model/CollectionDirectory";
import { Constants } from "../../util/constants";
import { ScopeNode } from "../../model/ScopeNode";
import { CollectionExistsError } from "couchbase";

export const createCollection = async (node: CollectionDirectory) => {
export const createCollection = async (node: ScopeNode) => {
const connection = Memory.state.get<IConnection>(Constants.ACTIVE_CONNECTION);
if (!connection) {
return;
Expand All @@ -44,10 +45,19 @@ export const createCollection = async (node: CollectionDirectory) => {
const collectionManager = await connection.cluster
?.bucket(node.bucketName)
.collections();
await collectionManager?.createCollection({
name: collectionName,
scopeName: node.scopeName,
});

logger.info(`${node.bucketName}: ${node.scopeName}: Successfully created the collection: ${collectionName}`);
try {
await collectionManager?.createCollection({
name: collectionName,
scopeName: node.scopeName,
});

logger.info(`${node.bucketName}: ${node.scopeName}: Successfully created the collection: ${collectionName}`);
} catch (error) {
logger.info(error);
if (error instanceof CollectionExistsError) {
vscode.window.showErrorMessage(`A collection with the name ${collectionName} already exists`);
logger.info(`${node.bucketName}: ${node.scopeName}: A collection with the name ${collectionName} already exists`);
}
}
};
3 changes: 3 additions & 0 deletions src/commands/collections/removeCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export const removeCollection = async (node: CollectionNode) => {
return;
}

// Remove any document filter if set
Memory.state.update(`filterDocuments-${node.connection.connectionIdentifier}-${node.bucketName}-${node.scopeName}-${node.collectionName}`, '');

const collectionManager = await connection.cluster
?.bucket(node.bucketName)
.collections();
Expand Down
64 changes: 58 additions & 6 deletions src/commands/documents/filterDocuments.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
import CollectionNode from "../../model/CollectionNode";
import * as vscode from 'vscode';
import * as vscode from "vscode";
import { Memory } from "../../util/util";
import { IFilterDocuments } from "../../types/IFilterDocuments";
import { logger } from "../../logger/logger";
import { ParsingFailureError } from "couchbase";

export const filterDocuments = async (node: CollectionNode) => {
const docFilter = Memory.state.get<IFilterDocuments>(`filterDocuments-${node.connection.connectionIdentifier}-${node.bucketName}-${node.scopeName}-${node.collectionName}`);
// 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
`;
// Execute the query
let primaryIndexExists = 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
vscode.window.showErrorMessage(
"Primary index doesn't exists for this document, Please create one before setting document filter"
);
logger.error(
"Error setting document filter: Primary index doesn't exists"
);
return false;
}
return true;
})
.catch((err) => {
logger.error("Error checking primary index: " + err);
return false;
});
if (!primaryIndexExists) {
return;
}

const docFilter = Memory.state.get<IFilterDocuments>(
`filterDocuments-${node.connection.connectionIdentifier}-${node.bucketName}-${node.scopeName}-${node.collectionName}`
);
const filterStmt: string = docFilter ? docFilter.filter : "";
let collectionName = node.collectionName;
if (collectionName.length > 15) {
collectionName = collectionName.substring(0, 13) + '...';
collectionName = collectionName.substring(0, 13) + "...";
}
const newDocFilterStmt = await vscode.window.showInputBox({
title: `Apply filter for collection \`${collectionName}\``,
Expand All @@ -18,7 +52,10 @@ export const filterDocuments = async (node: CollectionNode) => {
validateInput: (input) => {
const tokens = input.split(" ");
for (const token of tokens) {
if ((token.trim().toUpperCase() === "OFFSET") || (token.trim().toUpperCase() === "LIMIT")) {
if (
token.trim().toUpperCase() === "OFFSET" ||
token.trim().toUpperCase() === "LIMIT"
) {
return "The filters should not contain LIMIT and OFFSET";
}
}
Expand All @@ -28,11 +65,26 @@ export const filterDocuments = async (node: CollectionNode) => {
if (newDocFilterStmt === undefined) {
return;
}
try {
if (newDocFilterStmt.trim() !== "") {
await node.connection.cluster?.query(`SELECT META().id FROM \`${node.bucketName}\`.\`${node.scopeName}\`.\`${collectionName}\` WHERE ${newDocFilterStmt}`);
}
} catch (err) {
if (err instanceof ParsingFailureError) {
vscode.window.showErrorMessage(
"Parsing Failed: Incorrect filter definition"
);
} else {
logger.error(err);
}
return;
}

const newDocFilter: IFilterDocuments = {
filter: newDocFilterStmt.trim()
filter: newDocFilterStmt.trim(),
};
Memory.state.update(
`filterDocuments-${node.connection.connectionIdentifier}-${node.bucketName}-${node.scopeName}-${node.collectionName}`,
newDocFilter
);
};
};
32 changes: 16 additions & 16 deletions src/commands/indexes/openIndexInformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ import { MemFS } from "../../util/fileSystemProvider";
import IndexNode from "../../model/IndexNode";

export const openIndexInfo = async (indexNode: IndexNode, memFs: MemFS) => {
try {
const uri = vscode.Uri.parse(
`couchbase:/${indexNode.bucketName}/${indexNode.scopeName}/Indexes/${indexNode.indexName}.n1ql`
);
memFs.writeFile(
uri,
Buffer.from(indexNode.data),
{ create: true, overwrite: true }
);
const document = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(document, { preview: false });
return true;
} catch (err: any) {
logger.error("Failed to open index information");
logger.debug(err);
}
try {
const uri = vscode.Uri.parse(
`couchbase:/${indexNode.bucketName}/${indexNode.scopeName}/Indexes/${indexNode.indexName}.sqlpp`
);
memFs.writeFile(
uri,
Buffer.from(indexNode.data),
{ create: true, overwrite: true }
);
const document = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(document, { preview: false });
return true;
} catch (err: any) {
logger.error("Failed to open index information");
logger.debug(err);
}
};
73 changes: 36 additions & 37 deletions src/commands/queryHistory/applyQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,44 @@ export const applyQuery = async (query: IQuery) => {
return;
}
const activeTextEditor = vscode.window.activeTextEditor;
if (
activeTextEditor &&
activeTextEditor.document.languageId === "SQL++"
){
activeTextEditor.edit((editBuilder)=>{
editBuilder.replace(
new vscode.Range(0, 0, activeTextEditor.document.lineCount, 0),
query.query // Replace the entire content with the query
);
});
} else {
await vscode.commands.executeCommand(Commands.openQueryWorkbench);
// Wait for editor to become active, If exceeds 3 seconds, then error is thrown
let timeElapsed = 0; // in ms
let timeInterval = 200; // in ms
let totalTime = 3000; //in ms
while (true) {
await new Promise(resolve => setTimeout(resolve, timeInterval));

let newActiveTextEditor = vscode.window.activeTextEditor;

if (newActiveTextEditor && newActiveTextEditor.document.languageId === "SQL++") {
let lineCount = newActiveTextEditor.document.lineCount;
newActiveTextEditor.edit((editBuilder)=>{
editBuilder.replace(
new vscode.Range(0, 0, lineCount, 0),
query.query

);
});
break;
}
if (
activeTextEditor &&
activeTextEditor.document.languageId === "SQL++"
) {
activeTextEditor.edit((editBuilder) => {
editBuilder.replace(
new vscode.Range(0, 0, activeTextEditor.document.lineCount, 0),
query.query // Replace the entire content with the query
);
});
activeTextEditor.document.save();
} else {
await vscode.commands.executeCommand(Commands.openQueryWorkbench);
// Wait for editor to become active, If exceeds 3 seconds, then error is thrown
let timeElapsed = 0; // in ms
let timeInterval = 200; // in ms
let totalTime = 3000; //in ms
while (true) {
await new Promise(resolve => setTimeout(resolve, timeInterval));
let newActiveTextEditor = vscode.window.activeTextEditor;
if (newActiveTextEditor && newActiveTextEditor.document.languageId === "SQL++") {
let lineCount = newActiveTextEditor.document.lineCount;
newActiveTextEditor.edit((editBuilder) => {
editBuilder.replace(
new vscode.Range(0, 0, lineCount, 0),
query.query
);
});
newActiveTextEditor.document.save();
break;
}

timeElapsed += timeInterval;
timeElapsed += timeInterval;

if (timeElapsed >= totalTime) {
vscode.window.showErrorMessage("Unable to open workbench with selected query");
return;
}
if (timeElapsed >= totalTime) {
vscode.window.showErrorMessage("Unable to open workbench with selected query");
return;
}
}
}
};
18 changes: 13 additions & 5 deletions src/commands/scopes/createScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Memory } from "../../util/util";
import { logger } from "../../logger/logger";
import { BucketNode } from "../../model/BucketNode";
import { Constants } from "../../util/constants";
import { ScopeExistsError } from "couchbase";

export const createScope = async (node: BucketNode) => {
const connection = Memory.state.get<IConnection>(Constants.ACTIVE_CONNECTION);
Expand All @@ -40,10 +41,17 @@ export const createScope = async (node: BucketNode) => {
vscode.window.showErrorMessage(`Scope names cannot start with ${scopeName[0]}`);
return;
}
try {
const collectionManager = await connection.cluster
?.bucket(node.bucketName)
.collections();
await collectionManager?.createScope(scopeName);
logger.info(`${node.bucketName}: Successfully created the scope: ${scopeName}`);
} catch (error) {
if (error instanceof ScopeExistsError) {
vscode.window.showWarningMessage(`A scope with the name ${scopeName} already exists`);
logger.info(`${node.bucketName}: A scope with the name ${scopeName} already exists`);
}
}

const collectionManager = await connection.cluster
?.bucket(node.bucketName)
.collections();
await collectionManager?.createScope(scopeName);
logger.info(`${node.bucketName}: Successfully created the scope: ${scopeName}`);
};
3 changes: 1 addition & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import { QueryKernel } from "./notebook/controller";
import { Constants } from "./util/constants";
import { createNotebook } from "./notebook/notebook";
import IndexNode from "./model/IndexNode";
import { CollectionDirectory } from "./model/CollectionDirectory";
import { IndexDirectory } from "./model/IndexDirectory";
import { openIndexInfo } from "./commands/indexes/openIndexInformation";
import { Commands } from "./commands/extensionCommands/commands";
Expand Down Expand Up @@ -340,7 +339,7 @@ export function activate(context: vscode.ExtensionContext) {
subscriptions.push(
vscode.commands.registerCommand(
Commands.createCollection,
async (node: CollectionDirectory) => {
async (node: ScopeNode) => {
await createCollection(node);
clusterConnectionTreeProvider.refresh();
}
Expand Down
Loading

0 comments on commit 54a891f

Please sign in to comment.