Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@

All notable changes to Simple DB are documented in this file.

## Unreleased
## 0.1.2 - 2026-08-08

### Added

- One readable JSON file per database connection, stored locally by Simple DB.
- Automatic migration of existing 0.1.1 connection profiles to JSON files.
- `Open Connection JSON`, `Set Password`, and `Open Connections Folder` actions.
- A native `Simple DB` editor context submenu containing the six main database actions.
- A resizable `Simple DB` Results view in the lower VS Code Panel, with a SQL Developer-style data grid and row numbers.

### Changed

- Simplified connection creation: choose the engine and name, optionally enter a secure network password, then edit all non-secret connection parameters together in JSON.
- Passwords remain in VS Code `SecretStorage` and are explicitly rejected from connection JSON files.
- Query results now open below the SQL editor instead of in a separate editor tab, keeping the SQL document visible while results are inspected.

### Fixed

- Fixed the Results webview script error that could leave successful `SELECT` queries stuck on `Loading results…`.

## 0.1.1 - 2026-08-07

Expand Down
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Simple DB

Simple DB `0.1.1` 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.2` 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. `F5` launches `src/extension.js` directly: there is no TypeScript, `tsconfig.json`, `dist` folder, or compilation step.

Expand All @@ -17,11 +17,19 @@ The extension opens regular VS Code SQL documents. `F5` launches `src/extension.
- Generate `CREATE`, `ALTER`, and `DROP` scripts from the explorer and inspect object definitions/DDL.
- Use explicit per-editor transactions with `BEGIN`, `COMMIT`, and `ROLLBACK`.
- Cancel queries and configure query timeouts per connection.
- View results with multiple result sets, types, `NULL`, affected rows, duration, and cell/row/selection copy actions.
- View results below the SQL editor in a resizable SQL Developer-style grid with row numbers, multiple result sets, types, `NULL`, affected rows, duration, and cell/row/selection copy actions.
- Preserve 64-bit integers and high-precision `NUMBER` values exactly before displaying or exporting them.
- Export already-retrieved results to CSV, JSON, or XLSX without executing the SQL again.
- Use configurable local query history with reopen, copy, and rerun actions.

## Quick start

1. Open **Simple DB** in the Activity Bar and choose **Create Connection**.
2. Select the database engine and enter a connection name. SQLite uses the native file picker; network databases create a JSON profile with the correct default parameters.
3. Edit the generated JSON if needed, press `Ctrl+S`, then use **Test Connection** or **Connect** from the connection menu. Passwords stay outside JSON in VS Code `SecretStorage`.
4. Choose **New Query**, write SQL, and press `Ctrl+Enter` to execute the selection or the statement at the cursor. Use `Ctrl+Shift+Enter` to execute the entire document.
5. Results appear automatically in the resizable **Simple DB — Results** panel below the SQL editor. The same core commands are also available from the editor's **Simple DB** context submenu.

## Five database engines

| Engine | Driver | Notable dialect and explorer support |
Expand All @@ -32,7 +40,7 @@ The extension opens regular VS Code SQL documents. `F5` launches `src/extension.
| 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.1` 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.2` uses SQL authentication with a username and password for SQL Server.

## No imposed row limit by default

Expand Down Expand Up @@ -79,7 +87,9 @@ Each SQL editor has an independent session identifier. A transaction reserves it

## Results, copy, and export

The result panel supports page navigation, switching between multiple result sets, distinguishing `NULL`, selecting a cell or a range with `Shift`, and copying a cell, row, or selection.
Executing SQL automatically reveals the **Simple DB** view in VS Code's lower Panel while keeping the SQL editor visible above it. `SELECT` results are displayed in a spreadsheet-like grid with a sticky header, row numbers, horizontal/vertical scrolling, page navigation, and tabs for multiple result sets.

The result grid distinguishes `NULL`, shows column types, supports selecting a cell or a range with `Shift`, and can copy a cell, row, or selection. Non-query statements display their execution message and affected-row information in the same lower Results view.

CSV, JSON, and XLSX are streamed from the retrieved temporary result pages. The query is not executed again. Very large cell values are bounded for display by `simpleDb.maxCellCharacters`; the UI explicitly indicates when a cell has been truncated for display.

Expand Down Expand Up @@ -201,7 +211,7 @@ src/
storage/ # profiles, history, and paged results
test/ # JavaScript tests
ui/ # connection form
views/ # trees and result panel
views/ # trees and lower result grid
extension.js # activate/deactivate
```

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.1",
"version": "0.1.2",
"publisher": "suzdalenko-dev",
"license": "MIT",
"icon": "resources/simple-db-marketplace.png",
Expand Down Expand Up @@ -56,6 +56,7 @@
"activationEvents": [
"onView:simpleDb.connections",
"onView:simpleDb.history",
"onView:simpleDb.results",
"onLanguage:sql"
],
"main": "./src/extension.js",
Expand Down Expand Up @@ -246,6 +247,13 @@
"title": "Simple DB",
"icon": "resources/simple-db.svg"
}
],
"panel": [
{
"id": "simpleDbResults",
"title": "Simple DB",
"icon": "resources/simple-db.svg"
}
]
},
"views": {
Expand All @@ -260,6 +268,14 @@
"name": "History",
"type": "tree"
}
],
"simpleDbResults": [
{
"id": "simpleDb.results",
"name": "Results",
"type": "webview",
"contextualTitle": "Simple DB Results"
}
]
},
"menus": {
Expand Down
2 changes: 1 addition & 1 deletion src/core/valueNormalizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function truncateText(value, maxCharacters) {
}

const removed = value.length - maxCharacters;
return `${value.slice(0, maxCharacters)}… [${removed} caracteres omitidos]`;
return `${value.slice(0, maxCharacters)}… [${removed} characters omitted]`;
}

function normalizeValue(value, maxCharacters = 10000) {
Expand Down
8 changes: 7 additions & 1 deletion src/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const { ResultStore } = require('./storage/resultStore');
const { promptConnection, promptPassword } = require('./ui/connectionForm');
const { ConnectionsTreeProvider } = require('./views/connectionsTreeProvider');
const { HistoryTreeProvider } = require('./views/historyTreeProvider');
const { ResultPanel } = require('./views/resultPanel');
const { RESULT_VIEW_ID, ResultPanel } = require('./views/resultPanel');
const { getDatabaseEngine } = require('./databaseEngines');

let runtime = null;
Expand Down Expand Up @@ -150,6 +150,11 @@ async function activate(context) {
await resultStore.initialize();
const exportService = new ExportService(resultStore, exportConfiguration);
const resultPanel = new ResultPanel(resultStore, exportService);
const resultViewRegistration = vscode.window.registerWebviewViewProvider(
RESULT_VIEW_ID,
resultPanel,
{ webviewOptions: { retainContextWhenHidden: true } },
);
const queryRunner = new QueryRunner({
connectionStore,
connectionManager,
Expand All @@ -174,6 +179,7 @@ async function activate(context) {
context.subscriptions.push(
connectionsView,
historyView,
resultViewRegistration,
connectionsProvider,
historyProvider,
editorSessionManager,
Expand Down
2 changes: 1 addition & 1 deletion src/test/resultStore.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ describe('ResultStore', () => {
await store.finishSet('exec-2', 0);
const metadata = await store.finalizeExecution('exec-2');
expect(metadata.sets[0].pages).toBe(2);
expect((await store.getPage('exec-2', 0, 0))[0][0]).toContain('omitidos');
expect((await store.getPage('exec-2', 0, 0))[0][0]).toContain('characters omitted');
});
});
152 changes: 152 additions & 0 deletions src/test/resultViewHtml.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
'use strict';

const vm = require('node:vm');
const packageJson = require('../../package.json');
const { createResultViewHtml } = require('../views/resultViewHtml');

describe('lower Results view', () => {
it('generates valid JavaScript for the SQL result grid', () => {
const html = createResultViewHtml('test-nonce');
const match = html.match(
/<script nonce="test-nonce">([\s\S]*?)<\/script>/,
);

expect(match).not.toBeNull();
expect(() => new vm.Script(match[1])).not.toThrow();
expect(match[1]).toContain("lines.join('\\n')");
expect(html).toContain("numberCell.className = 'row-number'");
});

it('contributes Results as a webview in the VS Code Panel', () => {
const panel = packageJson.contributes.viewsContainers.panel;
const views = packageJson.contributes.views.simpleDbResults;

expect(packageJson.version).toBe('0.1.2');
expect(panel).toContainEqual(
expect.objectContaining({ id: 'simpleDbResults', title: 'Simple DB' }),
);
expect(views).toContainEqual(
expect.objectContaining({ id: 'simpleDb.results', type: 'webview' }),
);
});

it('renders SELECT metadata and returned rows in the lower grid', () => {
class FakeElement {
constructor(tagName = 'div') {
this.tagName = tagName.toUpperCase();
this.children = [];
this.dataset = {};
this.listeners = {};
this.className = '';
this.textContent = '';
this.hidden = false;
this.disabled = false;
this.value = '';
this.max = '';
this.classList = { toggle: () => {} };
}

appendChild(child) {
this.children.push(child);
return child;
}

replaceChildren(...children) {
this.children = children;
}

addEventListener(type, listener) {
this.listeners[type] = listener;
}

querySelectorAll() {
return [];
}
}

const elements = new Map();
const document = {
getElementById(id) {
if (!elements.has(id)) elements.set(id, new FakeElement());
return elements.get(id);
},
createElement(tagName) {
return new FakeElement(tagName);
},
querySelectorAll() {
return [];
},
};
const windowListeners = {};
const window = {
addEventListener(type, listener) {
windowListeners[type] = listener;
},
};
const posted = [];
const html = createResultViewHtml('render-test');
const scriptSource = html.match(
/<script nonce="render-test">([\s\S]*?)<\/script>/,
)[1];
const script = new vm.Script(scriptSource);

script.runInNewContext({
acquireVsCodeApi: () => ({ postMessage: (message) => posted.push(message) }),
document,
window,
});

expect(posted).toContainEqual({ type: 'ready' });
windowListeners.message({
data: {
type: 'metadata',
metadata: {
connectionName: 'Local SQLite',
totalRows: 1,
affectedRows: 0,
durationMs: 5,
status: 'success',
sets: [
{
kind: 'rows',
rowCount: 1,
pages: 1,
columns: [
{ name: 'id', type: 'INTEGER' },
{ name: 'name', type: 'TEXT' },
],
},
],
},
},
});

expect(posted).toContainEqual({ type: 'page', setIndex: 0, pageIndex: 0 });
windowListeners.message({
data: {
type: 'pageData',
setIndex: 0,
pageIndex: 0,
rowOffset: 0,
rows: [[1, 'Ada']],
},
});

const grid = elements.get('grid');
const allNodes = [];
const collect = (node) => {
allNodes.push(node);
node.children.forEach(collect);
};
grid.children.forEach(collect);
expect(allNodes).toEqual(
expect.arrayContaining([
expect.objectContaining({ className: 'row-number', textContent: '1' }),
expect.objectContaining({ tagName: 'TD', textContent: 'Ada' }),
]),
);
expect(elements.get('summary').textContent).toBe(
'Local SQLite · 1 row · 5 ms',
);
});
});
2 changes: 1 addition & 1 deletion src/test/valueNormalizer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('valueNormalizer', () => {
5,
);
expect(rows).toHaveLength(2);
expect(rows[0][0]).toContain('omitidos');
expect(rows[0][0]).toContain('characters omitted');
});

it('generates a safe display value for binary data', () => {
Expand Down
Loading
Loading