Skip to content

Commit

Permalink
Connections: Create SQLite connection (#6380)
Browse files Browse the repository at this point in the history
Addresses #6185
Additionaly, add support for checking and installing package
dependencies when they are not installed.
  • Loading branch information
dfalbel authored Feb 20, 2025
1 parent ec2e357 commit a1959ec
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 5 deletions.
191 changes: 186 additions & 5 deletions extensions/positron-connections/src/drivers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,107 @@ 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(), new PythonSQLiteDriver()]) {
context.subscriptions.push(
positron.connections.registerConnectionDriver(driver)
);
}
}

/// 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;
}

const 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;
}
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);
if (!installed) {
const install_succeed = await session.callMethod?.('install_packages', pkg);
if (!install_succeed) {
throw new Error('Failed to install dependencies');
}
}
}
}
return true;
}
}

class RPostgreSQLDriver implements positron.ConnectionsDriver {
class RPostgreSQLDriver extends RDriver implements positron.ConnectionsDriver {

constructor() {
super(['RPostgres', 'DBI']);
}

driverId: string = 'postgres';
metadata: positron.ConnectionsDriverMetadata = {
languageId: 'r',
Expand Down Expand Up @@ -83,10 +178,66 @@ con <- dbConnect(
)
`;
}
}

class RSQLiteDriver extends RDriver implements positron.ConnectionsDriver {

constructor() {
super(['RSQLite', 'DBI', 'connections']);
}

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 = ${JSON.stringify(dbname)},
bigint = ${JSON.stringify(bigint)}
)
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(
'r',
'python',
code,
true,
false,
Expand All @@ -100,3 +251,33 @@ con <- dbConnect(
}
}

class PythonSQLiteDriver extends PythonDriver implements positron.ConnectionsDriver {
driverId: string = 'py-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
`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,26 @@ export const CreateConnection = (props: PropsWithChildren<CreateConnectionProps>
);
}

if (props.selectedDriver.checkDependencies && props.selectedDriver.installDependencies) {
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();
}
}

await props.selectedDriver.connect(code);
} catch (err) {
services.notificationService.error(err);
Expand Down

0 comments on commit a1959ec

Please sign in to comment.