From 5d882e21d537a854121ef06e1ca010b523b8989c Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Mon, 17 Feb 2025 15:56:18 -0300 Subject: [PATCH 1/8] Add support for new SQLite connections --- .../positron-connections/src/drivers.ts | 65 ++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/extensions/positron-connections/src/drivers.ts b/extensions/positron-connections/src/drivers.ts index 8bdc06b95b1..f03b39bfebf 100644 --- a/extensions/positron-connections/src/drivers.ts +++ b/extensions/positron-connections/src/drivers.ts @@ -7,9 +7,11 @@ import * as positron from 'positron'; import * as vscode from 'vscode'; export function registerConnectionDrivers(context: vscode.ExtensionContext) { - context.subscriptions.push( - positron.connections.registerConnectionDriver(new RPostgreSQLDriver()) - ); + for (const driver of [new RSQLiteDriver(), new RPostgreSQLDriver()]) { + context.subscriptions.push( + positron.connections.registerConnectionDriver(driver) + ); + } } class RPostgreSQLDriver implements positron.ConnectionsDriver { @@ -100,3 +102,60 @@ con <- dbConnect( } } +class RSQLiteDriver implements positron.ConnectionsDriver { + driverId: string = 'sqlite'; + metadata: positron.ConnectionsDriverMetadata = { + languageId: 'r', + name: 'SQLite', + inputs: [ + { + 'id': 'dbname', + 'label': 'Database Name', + 'type': 'string', + 'value': 'database.db' + }, + { + 'id': 'bigint', + 'label': 'Integer representation', + 'type': 'option', + 'options': [ + { 'identifier': 'integer64', 'title': 'integer64' }, + { 'identifier': 'integer', 'title': 'integer' }, + { 'identifier': 'numeric', 'title': 'numeric' }, + { 'identifier': 'character', 'title': 'character' } + ], + 'value': 'integer64' + }, + ] + }; + + generateCode(inputs: positron.ConnectionsInput[]) { + const dbname = inputs.find(input => input.id === 'dbname')?.value; + const bigint = inputs.find(input => input.id === 'bigint')?.value; + + return `library(DBI) +con <- dbConnect( + RSQLite::SQLite(), + ${dbname ? `dbname = '${dbname}'` : ''}, + bigint = '${bigint ?? ''}' +) +connections::connection_view(con) +`; + } + + async connect(code: string) { + const exec = await positron.runtime.executeCode( + 'r', + code, + true, + false, + positron.RuntimeCodeExecutionMode.Interactive, + positron.RuntimeErrorBehavior.Continue + ); + if (!exec) { + throw new Error('Failed to execute code'); + } + return; + } +} + From 677494a28914e7c505045da9a4c61a60609ddf28 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Mon, 17 Feb 2025 16:51:25 -0300 Subject: [PATCH 2/8] Add support for installing deps --- .../positron-connections/src/drivers.ts | 121 +++++++++++++----- .../createConnectionState.tsx | 7 + 2 files changed, 97 insertions(+), 31 deletions(-) diff --git a/extensions/positron-connections/src/drivers.ts b/extensions/positron-connections/src/drivers.ts index f03b39bfebf..984c1834486 100644 --- a/extensions/positron-connections/src/drivers.ts +++ b/extensions/positron-connections/src/drivers.ts @@ -14,7 +14,91 @@ export function registerConnectionDrivers(context: vscode.ExtensionContext) { } } -class RPostgreSQLDriver implements positron.ConnectionsDriver { +/// A generic driver implementation +class RDriver implements positron.ConnectionsDriver { + + driverId: string = 'unknown'; + metadata: positron.ConnectionsDriverMetadata = { + languageId: 'r', + name: 'Unknown', + inputs: [] + }; + + constructor(readonly packages: string[]) { } + + async connect(code: string) { + const exec = await positron.runtime.executeCode( + 'r', + code, + true, + false, + positron.RuntimeCodeExecutionMode.Interactive, + positron.RuntimeErrorBehavior.Continue + ); + if (!exec) { + throw new Error('Failed to execute code'); + } + return; + } + + async checkDependencies() { + // Currently we skip dependency checks if there's no active R session + // in the foreground. + if (this.packages.length === 0) { + return true; + } + + let session = await positron.runtime.getForegroundSession() + if (session) { + + if (session.runtimeMetadata.languageId !== 'r') { + return true; + } + + for (const pkg of this.packages) { + const installed = await session.callMethod?.('is_installed', pkg) + if (!installed) { + return false; + } + } + } + + return true; + } + + async installDependencies() { + // Similar to checkDependencies, we skip dependency installation if there's + // no active R session in the foreground. + if (this.packages.length === 0) { + return true; + } + let session = await positron.runtime.getForegroundSession() + if (session) { + + if (session.runtimeMetadata.languageId !== 'r') { + return true; + } + + for (const pkg of this.packages) { + const installed = await session.callMethod?.('is_installed', pkg) + if (!installed) { + const installed = await session.callMethod?.('install_packages', pkg); + if (!installed) { + throw new Error('Failed to install dependencies'); + } + } + } + } + return true; + } +} + +class RPostgreSQLDriver extends RDriver implements positron.ConnectionsDriver { + + constructor() { + super(['RPostgres', 'DBI']); + } + driverId: string = 'postgres'; metadata: positron.ConnectionsDriverMetadata = { languageId: 'r', @@ -85,24 +169,14 @@ con <- dbConnect( ) `; } +} - async connect(code: string) { - const exec = await positron.runtime.executeCode( - 'r', - code, - true, - false, - positron.RuntimeCodeExecutionMode.Interactive, - positron.RuntimeErrorBehavior.Continue - ); - if (!exec) { - throw new Error('Failed to execute code'); - } - return; +class RSQLiteDriver extends RDriver implements positron.ConnectionsDriver { + + constructor() { + super(['RSQLite', 'DBI', 'connections']); } -} -class RSQLiteDriver implements positron.ConnectionsDriver { driverId: string = 'sqlite'; metadata: positron.ConnectionsDriverMetadata = { languageId: 'r', @@ -142,20 +216,5 @@ con <- dbConnect( connections::connection_view(con) `; } - - async connect(code: string) { - const exec = await positron.runtime.executeCode( - 'r', - code, - true, - false, - positron.RuntimeCodeExecutionMode.Interactive, - positron.RuntimeErrorBehavior.Continue - ); - if (!exec) { - throw new Error('Failed to execute code'); - } - return; - } } diff --git a/src/vs/workbench/contrib/positronConnections/browser/components/newConnectionModalDialog/createConnectionState.tsx b/src/vs/workbench/contrib/positronConnections/browser/components/newConnectionModalDialog/createConnectionState.tsx index 9f802f6ff4b..31bd41e177a 100644 --- a/src/vs/workbench/contrib/positronConnections/browser/components/newConnectionModalDialog/createConnectionState.tsx +++ b/src/vs/workbench/contrib/positronConnections/browser/components/newConnectionModalDialog/createConnectionState.tsx @@ -70,6 +70,13 @@ export const CreateConnection = (props: PropsWithChildren ); } + if (props.selectedDriver.checkDependencies && props.selectedDriver.installDependencies) { + const dependenciesInstalled = await props.selectedDriver.checkDependencies(); + if (!dependenciesInstalled) { + await props.selectedDriver.installDependencies(); + } + } + await props.selectedDriver.connect(code); } catch (err) { services.notificationService.error(err); From 45afd4993e4817ae563c96ad7a336a33491a9943 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 18 Feb 2025 09:33:09 -0300 Subject: [PATCH 3/8] Prompt to allow installation --- .../positron-connections/src/drivers.ts | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/extensions/positron-connections/src/drivers.ts b/extensions/positron-connections/src/drivers.ts index 984c1834486..f7cec61912d 100644 --- a/extensions/positron-connections/src/drivers.ts +++ b/extensions/positron-connections/src/drivers.ts @@ -48,7 +48,7 @@ class RDriver implements positron.ConnectionsDriver { return true; } - let session = await positron.runtime.getForegroundSession() + const session = await positron.runtime.getForegroundSession(); if (session) { if (session.runtimeMetadata.languageId !== 'r') { @@ -56,7 +56,7 @@ class RDriver implements positron.ConnectionsDriver { } for (const pkg of this.packages) { - const installed = await session.callMethod?.('is_installed', pkg) + const installed = await session.callMethod?.('is_installed', pkg); if (!installed) { return false; } @@ -72,18 +72,27 @@ class RDriver implements positron.ConnectionsDriver { if (this.packages.length === 0) { return true; } - let session = await positron.runtime.getForegroundSession() + const session = await positron.runtime.getForegroundSession(); if (session) { if (session.runtimeMetadata.languageId !== 'r') { return true; } + const allow_install = await positron.window.showSimpleModalDialogPrompt( + vscode.l10n.t("Installing dependencies"), + vscode.l10n.t("The following R packages are required for this connection: {0}. Would you like to install them now?", this.packages.join(', ')) + ); + + if (!allow_install) { + return false; + } + for (const pkg of this.packages) { - const installed = await session.callMethod?.('is_installed', pkg) + const installed = await session.callMethod?.('is_installed', pkg); if (!installed) { - const installed = await session.callMethod?.('install_packages', pkg); - if (!installed) { + const install_succeed = await session.callMethod?.('install_packages', pkg); + if (!install_succeed) { throw new Error('Failed to install dependencies'); } } @@ -159,7 +168,7 @@ class RPostgreSQLDriver extends RDriver implements positron.ConnectionsDriver { return `library(DBI) con <- dbConnect( - RPostgres::Postgres(), + RPostgres:: Postgres(), dbname = '${dbname ?? ''}', host = '${host ?? ''}', port = ${port ?? ''}, @@ -209,11 +218,11 @@ class RSQLiteDriver extends RDriver implements positron.ConnectionsDriver { return `library(DBI) con <- dbConnect( - RSQLite::SQLite(), + RSQLite:: SQLite(), ${dbname ? `dbname = '${dbname}'` : ''}, bigint = '${bigint ?? ''}' ) -connections::connection_view(con) +connections:: connection_view(con) `; } } From 62ba4c55fe7e2abee20f9ec4a7603a69892b53e4 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 18 Feb 2025 10:10:08 -0300 Subject: [PATCH 4/8] Fix spaces --- extensions/positron-connections/src/drivers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/positron-connections/src/drivers.ts b/extensions/positron-connections/src/drivers.ts index f7cec61912d..91d4f570811 100644 --- a/extensions/positron-connections/src/drivers.ts +++ b/extensions/positron-connections/src/drivers.ts @@ -168,7 +168,7 @@ class RPostgreSQLDriver extends RDriver implements positron.ConnectionsDriver { return `library(DBI) con <- dbConnect( - RPostgres:: Postgres(), + RPostgres::Postgres(), dbname = '${dbname ?? ''}', host = '${host ?? ''}', port = ${port ?? ''}, @@ -218,7 +218,7 @@ class RSQLiteDriver extends RDriver implements positron.ConnectionsDriver { return `library(DBI) con <- dbConnect( - RSQLite:: SQLite(), + RSQLite::SQLite(), ${dbname ? `dbname = '${dbname}'` : ''}, bigint = '${bigint ?? ''}' ) From 13e744fef470c534b7f6647abdc5f6821afe9584 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 18 Feb 2025 13:37:07 -0300 Subject: [PATCH 5/8] add Python SQlite support --- .../positron-connections/src/drivers.ts | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/extensions/positron-connections/src/drivers.ts b/extensions/positron-connections/src/drivers.ts index 91d4f570811..f827cebac95 100644 --- a/extensions/positron-connections/src/drivers.ts +++ b/extensions/positron-connections/src/drivers.ts @@ -7,7 +7,7 @@ import * as positron from 'positron'; import * as vscode from 'vscode'; export function registerConnectionDrivers(context: vscode.ExtensionContext) { - for (const driver of [new RSQLiteDriver(), new RPostgreSQLDriver()]) { + for (const driver of [new RSQLiteDriver(), new RPostgreSQLDriver(), new PythonSQLiteDriver()]) { context.subscriptions.push( positron.connections.registerConnectionDriver(driver) ); @@ -227,3 +227,57 @@ connections:: connection_view(con) } } +class PythonDriver implements positron.ConnectionsDriver { + driverId: string = 'python'; + metadata: positron.ConnectionsDriverMetadata = { + languageId: 'python', + name: 'Unknown', + inputs: [] + }; + + async connect(code: string) { + const exec = await positron.runtime.executeCode( + 'python', + code, + true, + false, + positron.RuntimeCodeExecutionMode.Interactive, + positron.RuntimeErrorBehavior.Continue + ); + if (!exec) { + throw new Error('Failed to execute code'); + } + return; + } +} + +class PythonSQLiteDriver extends PythonDriver implements positron.ConnectionsDriver { + driverId: string = 'sqlite'; + metadata: positron.ConnectionsDriverMetadata = { + languageId: 'python', + name: 'SQLite', + inputs: [ + { + 'id': 'dbname', + 'label': 'Database Name', + 'type': 'string', + 'value': 'database.db' + }, + { + 'id': 'timeout', + 'label': 'Timeout', + 'type': 'number', + 'value': '5.0' + }, + ] + }; + + generateCode(inputs: positron.ConnectionsInput[]) { + const dbname = inputs.find(input => input.id === 'dbname')?.value; + + return `import sqlite3 +conn = sqlite3.connect(${JSON.stringify(dbname) ?? JSON.stringify('')}) +%connection_show conn +`; + } +} From fd1a68a47229403930fdf275ecfd7c3537e26d77 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 18 Feb 2025 13:41:05 -0300 Subject: [PATCH 6/8] add path escaping --- extensions/positron-connections/src/drivers.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/positron-connections/src/drivers.ts b/extensions/positron-connections/src/drivers.ts index f827cebac95..a5248332b2b 100644 --- a/extensions/positron-connections/src/drivers.ts +++ b/extensions/positron-connections/src/drivers.ts @@ -213,14 +213,14 @@ class RSQLiteDriver extends RDriver implements positron.ConnectionsDriver { }; generateCode(inputs: positron.ConnectionsInput[]) { - const dbname = inputs.find(input => input.id === 'dbname')?.value; - const bigint = inputs.find(input => input.id === 'bigint')?.value; + const dbname = inputs.find(input => input.id === 'dbname')?.value ?? ''; + const bigint = inputs.find(input => input.id === 'bigint')?.value ?? ''; return `library(DBI) con <- dbConnect( RSQLite::SQLite(), - ${dbname ? `dbname = '${dbname}'` : ''}, - bigint = '${bigint ?? ''}' + dbname = ${JSON.stringify(dbname)}, + bigint = ${JSON.stringify(bigint)} ) connections:: connection_view(con) `; @@ -252,7 +252,7 @@ class PythonDriver implements positron.ConnectionsDriver { } class PythonSQLiteDriver extends PythonDriver implements positron.ConnectionsDriver { - driverId: string = 'sqlite'; + driverId: string = 'py-sqlite'; metadata: positron.ConnectionsDriverMetadata = { languageId: 'python', name: 'SQLite', From 9a68a6064903a2707dfa62cd8d43f560542360ac Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 19 Feb 2025 11:18:38 -0300 Subject: [PATCH 7/8] Handle failure to check for dependencies --- .../createConnectionState.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/positronConnections/browser/components/newConnectionModalDialog/createConnectionState.tsx b/src/vs/workbench/contrib/positronConnections/browser/components/newConnectionModalDialog/createConnectionState.tsx index 31bd41e177a..69953dc466c 100644 --- a/src/vs/workbench/contrib/positronConnections/browser/components/newConnectionModalDialog/createConnectionState.tsx +++ b/src/vs/workbench/contrib/positronConnections/browser/components/newConnectionModalDialog/createConnectionState.tsx @@ -71,7 +71,20 @@ export const CreateConnection = (props: PropsWithChildren } if (props.selectedDriver.checkDependencies && props.selectedDriver.installDependencies) { - const dependenciesInstalled = await props.selectedDriver.checkDependencies(); + let dependenciesInstalled = false; + try { + dependenciesInstalled = await props.selectedDriver.checkDependencies(); + } catch (err) { + services.notificationService.error(localize( + 'positron.newConnectionModalDialog.createConnection.failedToCheckDependencies', + 'Failed to check if dependencies are installed: {}', + err + )); + // If we fail to check if dependencies are installed, we presume they are installed + // and let the user try to connect anyway so they don't get blocked. + dependenciesInstalled = true; + } + if (!dependenciesInstalled) { await props.selectedDriver.installDependencies(); } From 2f7145034ed48033f6e4ec849fc40b56ea50715e Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Thu, 20 Feb 2025 10:08:11 -0300 Subject: [PATCH 8/8] Remove whitespace --- extensions/positron-connections/src/drivers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/positron-connections/src/drivers.ts b/extensions/positron-connections/src/drivers.ts index a5248332b2b..fac8a17d1aa 100644 --- a/extensions/positron-connections/src/drivers.ts +++ b/extensions/positron-connections/src/drivers.ts @@ -222,7 +222,7 @@ con <- dbConnect( dbname = ${JSON.stringify(dbname)}, bigint = ${JSON.stringify(bigint)} ) -connections:: connection_view(con) +connections::connection_view(con) `; } }