diff --git a/CHANGELOG.md b/CHANGELOG.md index f91082c..025157f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to Simple DB are documented in this file. +## 0.1.6 - 2026-08-08 + +### Added + +- The complete connection and query workflow to the **Simple DB** right-click submenu of regular SQL files: create/select/connect/disconnect/test/edit/delete/refresh connections, create queries, run statement/selection/script, cancel, and transaction controls. +- **Go to Package Specification** and **Go to Package Body** for explicit Oracle package navigation. + +### Changed + +- Connection commands launched from a SQL editor now use the connection attached to that file. If the file has no connection yet, Simple DB opens the same cancellable connection picker first. +- The Command Palette still exposes only **Simple DB: Open Configuration**. + ## 0.1.5 - 2026-08-08 ### Added diff --git a/README.md b/README.md index 0e31432..3a1bc11 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Simple DB -Simple DB `0.1.5` is a Visual Studio Code extension written entirely in JavaScript for working with **SQLite, PostgreSQL, MySQL, SQL Server, and Oracle** through a single interface. +Simple DB `0.1.6` is a Visual Studio Code extension written entirely in JavaScript for working with **SQLite, PostgreSQL, MySQL, SQL Server, and Oracle** through a single interface. The extension opens regular VS Code SQL documents and loads `src/extension.js` directly: there is no TypeScript, `tsconfig.json`, `dist` folder, or compilation step. @@ -42,7 +42,7 @@ The extension opens regular VS Code SQL documents and loads `src/extension.js` d | SQL Server | `mssql` | databases, schemas, tables, views, procedures/functions, indexes, triggers, sequences, types, synonyms, `GO` | | Oracle | `oracledb` Thin | schemas, tables, views/materialized views, procedures/functions, packages, indexes, triggers, sequences, types, synonyms, PL/SQL | -Oracle uses the default `node-oracledb` Thin mode, so normal connections do not require Oracle Client to be installed. Simple DB `0.1.5` uses SQL authentication with a username and password for SQL Server. +Oracle uses the default `node-oracledb` Thin mode, so normal connections do not require Oracle Client to be installed. Simple DB `0.1.6` uses SQL authentication with a username and password for SQL Server. ## No imposed row limit by default @@ -143,20 +143,34 @@ The `id` is generated and managed by Simple DB. Do not change it. Use **Simple D ### Editor context menu -Right-clicking inside an editor now shows a native **Simple DB** submenu with the main actions: +Right-clicking inside any regular SQL editor shows a native **Simple DB** submenu with the complete working set: - Open Configuration +- Refresh Connections +- Open Connections Folder - Select / Change Connection... +- Create Connection +- Connect / Disconnect +- Test Connection +- Open Connection JSON +- Set Password +- Delete Connection +- New Query - Run Statement (`Ctrl+Enter`) +- Execute Selection - Run Script (`F5`) +- Cancel Query +- Begin Transaction / Commit / Rollback - Go to Definition (`F12`) - Go to Declaration +- Go to Package Specification +- Go to Package Body -**Open Configuration** opens the same Simple DB Activity Bar view as clicking the Simple DB icon, where all configured database connections and their explorer actions are available. The Command Palette intentionally exposes only **Simple DB: Open Configuration**; SQL-file actions stay in this right-click submenu, their keyboard shortcuts, and the SQL editor controls. +**Open Configuration** opens the same Simple DB Activity Bar view as clicking the Simple DB icon, where all configured database connections and their explorer actions are available. The Command Palette intentionally exposes only **Simple DB: Open Configuration**; SQL-file actions stay in this right-click submenu, their keyboard shortcuts, and the SQL editor controls. Connection actions invoked from a SQL file operate on the connection attached to that file. If no connection is attached yet, Simple DB opens the connection picker first. The lower-right status bar shows the profile currently attached to the SQL file. **Select / Change Connection...** marks that profile in the picker and lets the file switch to another one. If there are no profiles yet, **Create New Connection...** is available directly in the picker. The picker and connection-creation prompts can be cancelled with **Cancel / Close**, `Esc`, or by clicking outside. Cancelling nested connection creation closes the selection flow instead of reopening it. -Navigation resolves against the connection attached to the SQL file and opens database-provided source/DDL in a read-only virtual SQL document. Oracle package **Go to Declaration** targets the package specification while **Go to Definition** targets the package body. When an overload can be identified from the call arguments, Simple DB selects it; when several overloads remain valid, it presents all valid locations instead of guessing. `alias.column` references are resolved back to their underlying table/view column, Oracle and SQL Server synonyms are followed to the target object when it is local/resolvable, and navigation can continue from one opened database definition into another. If catalog/source permissions are insufficient, Simple DB reports that explicitly. +Navigation resolves against the connection attached to the SQL file and opens database-provided source/DDL in a read-only virtual SQL document. Oracle package **Go to Declaration** targets the package specification while **Go to Definition** targets the package body. **Go to Package Specification** and **Go to Package Body** provide explicit package-only navigation when the symbol under the cursor resolves to an Oracle package or package member. When an overload can be identified from the call arguments, Simple DB selects it; when several overloads remain valid, it presents all valid locations instead of guessing. `alias.column` references are resolved back to their underlying table/view column, Oracle and SQL Server synonyms are followed to the target object when it is local/resolvable, and navigation can continue from one opened database definition into another. If catalog/source permissions are insufficient, Simple DB reports that explicitly. ### SQLite note diff --git a/package-lock.json b/package-lock.json index 0ea8ef0..6232aa5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "simple-db-suzdalenko", - "version": "0.1.5", + "version": "0.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simple-db-suzdalenko", - "version": "0.1.5", + "version": "0.1.6", "license": "MIT", "dependencies": { "exceljs": "4.4.0", diff --git a/package.json b/package.json index fd60b57..52944b3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "simple-db-suzdalenko", "displayName": "Simple DB - SQL Client for SQLite, MySQL, PostgreSQL, SQL Server & Oracle", "description": "SQL client and database editor for SQLite, MySQL, PostgreSQL, SQL Server (MSSQL) and Oracle. Browse database objects, run queries, manage DDL and export results.", - "version": "0.1.5", + "version": "0.1.6", "publisher": "suzdalenko-dev", "license": "MIT", "icon": "resources/simple-db-marketplace.png", @@ -160,6 +160,16 @@ "title": "Go to Declaration", "category": "Simple DB" }, + { + "command": "simpleDb.goToPackageSpecification", + "title": "Go to Package Specification", + "category": "Simple DB" + }, + { + "command": "simpleDb.goToPackageBody", + "title": "Go to Package Body", + "category": "Simple DB" + }, { "command": "simpleDb.cancelQuery", "title": "Cancel Query", @@ -323,6 +333,14 @@ "command": "simpleDb.goToDeclaration", "when": "false" }, + { + "command": "simpleDb.goToPackageSpecification", + "when": "false" + }, + { + "command": "simpleDb.goToPackageBody", + "when": "false" + }, { "command": "simpleDb.cancelQuery", "when": "false" @@ -493,30 +511,115 @@ "when": "editorLangId == sql", "group": "1_configuration@1" }, + { + "command": "simpleDb.refreshConnections", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "1_configuration@2" + }, + { + "command": "simpleDb.openConnectionsFolder", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "1_configuration@3" + }, { "command": "simpleDb.changeEditorConnection", "when": "editorLangId == sql && resourceScheme != simple-db-definition", "group": "2_connection@1" }, { - "command": "simpleDb.executeCurrent", + "command": "simpleDb.addConnection", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "2_connection@2" + }, + { + "command": "simpleDb.connect", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "2_connection@3" + }, + { + "command": "simpleDb.disconnect", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "2_connection@4" + }, + { + "command": "simpleDb.testConnection", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "2_connection@5" + }, + { + "command": "simpleDb.editConnection", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "2_connection@6" + }, + { + "command": "simpleDb.setPassword", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "2_connection@7" + }, + { + "command": "simpleDb.deleteConnection", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "2_connection@8" + }, + { + "command": "simpleDb.newQuery", "when": "editorLangId == sql && resourceScheme != simple-db-definition", "group": "3_run@1" }, { - "command": "simpleDb.executeDocument", + "command": "simpleDb.executeCurrent", "when": "editorLangId == sql && resourceScheme != simple-db-definition", "group": "3_run@2" }, + { + "command": "simpleDb.executeSelection", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "3_run@3" + }, + { + "command": "simpleDb.executeDocument", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "3_run@4" + }, + { + "command": "simpleDb.cancelQuery", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "3_run@5" + }, + { + "command": "simpleDb.beginTransaction", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "4_transaction@1" + }, + { + "command": "simpleDb.commit", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "4_transaction@2" + }, + { + "command": "simpleDb.rollback", + "when": "editorLangId == sql && resourceScheme != simple-db-definition", + "group": "4_transaction@3" + }, { "command": "simpleDb.goToDefinition", "when": "editorLangId == sql", - "group": "4_navigation@1" + "group": "5_navigation@1" }, { "command": "simpleDb.goToDeclaration", "when": "editorLangId == sql", - "group": "4_navigation@2" + "group": "5_navigation@2" + }, + { + "command": "simpleDb.goToPackageSpecification", + "when": "editorLangId == sql", + "group": "5_navigation@3" + }, + { + "command": "simpleDb.goToPackageBody", + "when": "editorLangId == sql", + "group": "5_navigation@4" } ] }, diff --git a/src/extension.js b/src/extension.js index 202aa89..a541aa3 100644 --- a/src/extension.js +++ b/src/extension.js @@ -61,6 +61,23 @@ async function pickProfile(connectionStore, title = 'Simple DB — Select Connec return pick && !pick.cancel ? pick.profile : null; } +async function profileForCommand(connectionStore, editorSessionManager, node) { + const nodeProfile = profileForNode(connectionStore, node); + if (nodeProfile) return nodeProfile; + + const editor = vscode.window.activeTextEditor; + if ( + editor?.document?.languageId === 'sql' && + editor.document.uri.scheme !== DEFINITION_SCHEME + ) { + const session = await editorSessionManager.ensureSession(editor.document); + if (!session) return null; + return connectionStore.get(session.profileId) || null; + } + + return pickProfile(connectionStore); +} + function databaseContext(profile, node) { return ( node?.database || @@ -235,8 +252,8 @@ async function activate(context) { }); registerCommand(context, 'simpleDb.connect', async (node) => { - const profile = profileForNode(connectionStore, node); - if (!profile) throw new Error('Connection not found.'); + const profile = await profileForCommand(connectionStore, editorSessionManager, node); + if (!profile) return; const adapter = await connectionManager.connect(profile.id); vscode.window.showInformationMessage( `Simple DB: connected to ${profile.name} · ${adapter.serverVersion}`, @@ -244,8 +261,8 @@ async function activate(context) { }); registerCommand(context, 'simpleDb.disconnect', async (node) => { - const profile = profileForNode(connectionStore, node); - if (!profile) throw new Error('Connection not found.'); + const profile = await profileForCommand(connectionStore, editorSessionManager, node); + if (!profile) return; const transactionCount = connectionManager.transactionCount(profile.id); const executionCount = connectionManager.executionCount(profile.id); if (transactionCount > 0 || executionCount > 0) { @@ -264,8 +281,8 @@ async function activate(context) { }); registerCommand(context, 'simpleDb.testConnection', async (node) => { - const profile = profileForNode(connectionStore, node) || (await pickProfile(connectionStore)); - if (!profile) throw new Error('Connection not found.'); + const profile = await profileForCommand(connectionStore, editorSessionManager, node); + if (!profile) return; const result = await connectionManager.testConnection(profile.id); vscode.window.showInformationMessage( `Simple DB: ${profile.name} responded in ${result.elapsedMs} ms · ${result.serverVersion}`, @@ -273,8 +290,8 @@ async function activate(context) { }); registerCommand(context, 'simpleDb.editConnection', async (node) => { - const profile = profileForNode(connectionStore, node); - if (!profile) throw new Error('Connection not found.'); + const profile = await profileForCommand(connectionStore, editorSessionManager, node); + if (!profile) return; if (connectionManager.isConnected(profile.id)) { const transactions = connectionManager.transactionCount(profile.id); const executions = connectionManager.executionCount(profile.id); @@ -298,7 +315,7 @@ async function activate(context) { }); registerCommand(context, 'simpleDb.setPassword', async (node) => { - const profile = profileForNode(connectionStore, node) || (await pickProfile(connectionStore)); + const profile = await profileForCommand(connectionStore, editorSessionManager, node); if (!profile) return; if (profile.engine === 'sqlite') { vscode.window.showInformationMessage('Simple DB: SQLite connections do not use a password.'); @@ -320,8 +337,8 @@ async function activate(context) { }); registerCommand(context, 'simpleDb.deleteConnection', async (node) => { - const profile = profileForNode(connectionStore, node); - if (!profile) throw new Error('Connection not found.'); + const profile = await profileForCommand(connectionStore, editorSessionManager, node); + if (!profile) return; const transactions = connectionManager.transactionCount(profile.id); const executions = connectionManager.executionCount(profile.id); const warning = transactions > 0 || executions > 0 @@ -341,7 +358,7 @@ async function activate(context) { }); registerCommand(context, 'simpleDb.newQuery', async (node) => { - const profile = profileForNode(connectionStore, node) || (await pickProfile(connectionStore)); + const profile = await profileForCommand(connectionStore, editorSessionManager, node); if (!profile) return; const database = databaseContext(profile, node); const schema = schemaContext(profile, node, database); @@ -358,6 +375,18 @@ async function activate(context) { registerCommand(context, 'simpleDb.goToDeclaration', () => sqlNavigationProvider.openFromActiveEditor('declaration'), ); + registerCommand(context, 'simpleDb.goToPackageSpecification', () => + sqlNavigationProvider.openFromActiveEditor('declaration', { + objectType: 'package', + label: 'package specification', + }), + ); + registerCommand(context, 'simpleDb.goToPackageBody', () => + sqlNavigationProvider.openFromActiveEditor('definition', { + objectType: 'package', + label: 'package body', + }), + ); registerCommand(context, 'simpleDb.executeCurrent', () => queryRunner.run('current')); registerCommand(context, 'simpleDb.executeSelection', () => diff --git a/src/test/manifest.test.js b/src/test/manifest.test.js index 7db507f..931bdfd 100644 --- a/src/test/manifest.test.js +++ b/src/test/manifest.test.js @@ -3,8 +3,8 @@ const manifest = require('../../package.json'); describe('extension manifest', () => { - it('ships 0.1.5 without the removed History feature', () => { - expect(manifest.version).toBe('0.1.5'); + it('ships 0.1.6 without the removed History feature', () => { + expect(manifest.version).toBe('0.1.6'); expect(JSON.stringify(manifest).toLowerCase()).not.toContain('history'); }); @@ -46,15 +46,32 @@ describe('extension manifest', () => { }); }); - it('keeps the SQL editor context menu focused on Simple DB daily actions', () => { + it('exposes the complete Simple DB workflow from the SQL editor context menu', () => { const menu = manifest.contributes.menus['simpleDb.editorContextMenu']; expect(menu.map((entry) => entry.command)).toEqual([ 'simpleDb.openConfiguration', + 'simpleDb.refreshConnections', + 'simpleDb.openConnectionsFolder', 'simpleDb.changeEditorConnection', + 'simpleDb.addConnection', + 'simpleDb.connect', + 'simpleDb.disconnect', + 'simpleDb.testConnection', + 'simpleDb.editConnection', + 'simpleDb.setPassword', + 'simpleDb.deleteConnection', + 'simpleDb.newQuery', 'simpleDb.executeCurrent', + 'simpleDb.executeSelection', 'simpleDb.executeDocument', + 'simpleDb.cancelQuery', + 'simpleDb.beginTransaction', + 'simpleDb.commit', + 'simpleDb.rollback', 'simpleDb.goToDefinition', 'simpleDb.goToDeclaration', + 'simpleDb.goToPackageSpecification', + 'simpleDb.goToPackageBody', ]); }); diff --git a/src/test/resultViewHtml.test.js b/src/test/resultViewHtml.test.js index 4c8fab6..63502b5 100644 --- a/src/test/resultViewHtml.test.js +++ b/src/test/resultViewHtml.test.js @@ -21,7 +21,7 @@ describe('lower Results view', () => { const panel = packageJson.contributes.viewsContainers.panel; const views = packageJson.contributes.views.simpleDbResults; - expect(packageJson.version).toBe('0.1.5'); + expect(packageJson.version).toBe('0.1.6'); expect(panel).toContainEqual( expect.objectContaining({ id: 'simpleDbResults', title: 'Simple DB' }), ); diff --git a/src/views/sqlNavigationProvider.js b/src/views/sqlNavigationProvider.js index b028c2e..b1f9109 100644 --- a/src/views/sqlNavigationProvider.js +++ b/src/views/sqlNavigationProvider.js @@ -119,7 +119,7 @@ class SqlNavigationProvider { return new vscode.Location(record.uri, positionAt(record.content, offset)); } - async _locations(document, position, token, mode) { + async _locations(document, position, token, mode, options = {}) { if (token.isCancellationRequested) return null; const reference = extractSqlReference( document.getText(), @@ -139,10 +139,13 @@ class SqlNavigationProvider { reference, mode, ); - if (!targets.length || token.isCancellationRequested) return null; + const filteredTargets = options.objectType + ? targets.filter((target) => target.objectType === options.objectType) + : targets; + if (!filteredTargets.length || token.isCancellationRequested) return null; const localDocuments = new Map(); - return targets.map((target) => + return filteredTargets.map((target) => this._virtualLocation(context.profile, target, localDocuments), ); } @@ -185,7 +188,7 @@ class SqlNavigationProvider { }); } - async openFromActiveEditor(mode) { + async openFromActiveEditor(mode, options = {}) { const editor = vscode.window.activeTextEditor; if (!editor || editor.document.languageId !== 'sql') { throw new Error('Open a SQL editor before navigating to a database object.'); @@ -197,14 +200,16 @@ class SqlNavigationProvider { editor.selection.active, cancellationToken(), mode, + options, ); } catch (error) { this._reportError(error, mode); return; } if (!locations?.length) { + const targetLabel = options.label || mode; vscode.window.showInformationMessage( - 'Simple DB: no ' + mode + ' was found for the symbol under the cursor.', + 'Simple DB: no ' + targetLabel + ' was found for the symbol under the cursor.', ); return; } @@ -230,8 +235,13 @@ class SqlNavigationProvider { location, }); } - const title = - mode === 'declaration' ? 'Simple DB — Choose Declaration' : 'Simple DB — Choose Definition'; + const title = options.label + ? 'Simple DB — Choose ' + + options.label.charAt(0).toUpperCase() + + options.label.slice(1) + : mode === 'declaration' + ? 'Simple DB — Choose Declaration' + : 'Simple DB — Choose Definition'; const pick = await vscode.window.showQuickPick(items, { title, placeHolder: 'Multiple database objects or overloads match this reference',