in gui/extension/src/DocumentCommandHandler.ts [748:1159]
id: uuid(),
language: this.languageFromConnection(connection),
caption: "Selected SQL",
content: sql,
});
}
}
});
}
}));
context.subscriptions.push(commands.registerCommand("msg.closeEditor", (
entry?: IOdmNotebookEntry | IOdmScriptEntry) => {
if (entry?.parent) {
const provider = entry.parent.parent?.provider;
const connection = entry.parent;
if (provider instanceof DBConnectionViewProvider) {
void provider.closeEditor(connection.details.id, entry.id, entry.parent.id);
}
}
}));
context.subscriptions.push(commands.registerCommand("msg.newNotebookMysql",
(entry?: IOdmConnectionPageEntry) => {
void this.createNewEditor({ entry, language: "msg" });
}));
context.subscriptions.push(commands.registerCommand("msg.newNotebookSqlite",
(entry?: IOdmConnectionPageEntry) => {
void this.createNewEditor({ entry, language: "msg" });
}));
context.subscriptions.push(commands.registerCommand("msg.newScriptJs", (entry?: IOdmConnectionPageEntry) => {
void this.createNewEditor({ entry, language: "javascript" });
}));
context.subscriptions.push(commands.registerCommand("msg.newScriptMysql", (entry?: IOdmConnectionPageEntry) => {
void this.createNewEditor({ entry, language: "mysql" });
}));
context.subscriptions.push(commands.registerCommand("msg.newScriptSqlite",
(entry?: IOdmConnectionPageEntry) => {
void this.createNewEditor({ entry, language: "sql" });
}));
context.subscriptions.push(commands.registerCommand("msg.newScriptTs", (entry?: IOdmConnectionPageEntry) => {
void this.createNewEditor({ entry, language: "typescript" });
}));
context.subscriptions.push(commands.registerCommand("msg.mrs.addDbObject", (
entry?: ICdmTableEntry | ICdmViewEntry | ICdmRoutineEntry) => {
if (entry) {
const connection = entry.parent.parent.parent;
if (entry.type === CdmEntityType.Table || entry.type === CdmEntityType.View
|| entry.type === CdmEntityType.StoredFunction || entry.type === CdmEntityType.StoredProcedure) {
// First, create a new temporary dbObject, then call the DbObject dialog
this.createNewDbObject(connection.backend, entry).then((dbObject) => {
const provider = this.#host.currentProvider;
void provider?.editMrsDbObject(connection.details.id,
{ dbObject, createObject: true });
}).catch((reason) => {
void ui.showErrorMessage(`${String(reason)}`, {});
});
} else {
void ui.showErrorMessage(`The database object type '${entry.caption}' is not supported at ` +
`this time`, {});
}
}
}));
context.subscriptions.push(commands.registerCommand("msg.mrs.editDbObject", (entry?: ICdmRestDbObjectEntry) => {
if (entry) {
const provider = this.#host.currentProvider;
const connection = entry.parent.parent.parent.parent;
void provider?.editMrsDbObject(connection.details.id,
{ dbObject: entry.details, createObject: false });
}
}));
context.subscriptions.push(commands.registerCommand("msg.newSession", () => {
const provider = this.#host.currentProvider;
void provider?.openSession({ sessionId: uuid() });
}));
context.subscriptions.push(commands.registerCommand("msg.openSession", (details: IShellSessionDetails) => {
const provider = this.#host.currentProvider;
void provider?.openSession(details);
}));
context.subscriptions.push(commands.registerCommand("msg.newSessionUsingConnection",
(entry: ICdmConnectionEntry | IOdmConnectionPageEntry) => {
const provider = this.#host.currentProvider;
const caption = entry.type === CdmEntityType.Connection ? entry.details.caption : entry.caption;
const dbConnectionId = entry.details.id;
const details: IShellSessionDetails = {
sessionId: uuid(),
caption,
dbConnectionId,
};
void provider?.openSession(details);
}));
context.subscriptions.push(commands.registerCommand("msg.removeSession", (entry: IOdmShellSessionEntry) => {
const provider = entry.parent?.parent?.provider;
if (provider instanceof DBConnectionViewProvider) {
void provider.removeSession(entry.details);
}
}));
}
public async addNewSqlScript(connectionId: number, command: string, schemaName: string,
scriptName: string, placeHolder: string): Promise<void> {
let name: string | undefined = "";
let sql = "";
// If the commands is a create command, get the name of the new routine
if (command.startsWith("msg.create")) {
name = await window.showInputBox({
title: `New Routine on Schema \`${schemaName}\``,
placeHolder,
prompt: "Please enter a name for the new routine:",
value: "",
});
if (name === undefined) {
return;
}
if (name === "") {
name = placeHolder;
}
}
switch (command) {
case "msg.createProcedure": {
sql = `DELIMITER %%\nDROP PROCEDURE IF EXISTS \`${schemaName}\`.\`${name}\`%%\n`
+ `/* Add or remove procedure IN/OUT/INOUT parameters as needed. */\n`
+ `CREATE PROCEDURE \`${schemaName}\`.\`${name}\`(IN arg1 INTEGER, OUT arg2 INTEGER)\n`
+ `SQL SECURITY DEFINER\nNOT DETERMINISTIC\nBEGIN\n`
+ ` /* Insert the procedure code here. */\n SET arg2 = arg1 * 2;\n`
+ `END%%\nDELIMITER ;\n\n`
+ `CALL \`${schemaName}\`.\`${name}\`(1, @arg2);\nSELECT @arg2;`;
break;
}
case "msg.createFunction": {
sql = `DELIMITER %%\nDROP FUNCTION IF EXISTS \`${schemaName}\`.\`${name}\`%%\n`
+ `/* Add or remove function parameters as needed. */\n`
+ `CREATE FUNCTION \`${schemaName}\`.\`${name}\`(arg1 INTEGER)\nRETURNS INTEGER\n`
+ `SQL SECURITY DEFINER\nDETERMINISTIC\nBEGIN\n`
+ ` /* Insert the function code here. */\n return arg1;\nEND%%\nDELIMITER ;`
+ `\n\nSELECT \`${schemaName}\`.\`${name}\`(1);`;
break;
}
case "msg.createFunctionJs": {
sql = `DROP FUNCTION IF EXISTS \`${schemaName}\`.\`${name}\`;\n`
+ `/* Add or remove function parameters as needed. */\n`
+ `CREATE FUNCTION \`${schemaName}\`.\`${name}\`(arg1 INTEGER)\n`
+ `RETURNS INTEGER\n`
+ `SQL SECURITY DEFINER\n`
+ `DETERMINISTIC LANGUAGE JAVASCRIPT\nAS $$\n`
+ ` /* Insert the function code here. */\n`
+ ` console.log("Hello World!");\n`
+ ` console.log('{"info": "This is Javascript"}');\n`
+ ` /* throw("Custom Error"); */\n`
+ ` return arg1;\n`
+ `$$;\n`
+ `SELECT \`${schemaName}\`.\`${name}\`(1);`;
break;
}
case "msg.createProcedureJs": {
sql = `DROP PROCEDURE IF EXISTS \`${schemaName}\`.\`${name}\`;\n`
+ `/* Add or remove procedure parameters as needed. */\n`
+ `CREATE PROCEDURE \`${schemaName}\`.\`${name}\`(IN arg1 INTEGER, OUT arg2 INTEGER)\n`
+ `DETERMINISTIC LANGUAGE JAVASCRIPT\nAS $$\n`
+ ` /* Insert the procedure code here. */\n`
+ ` console.log("Hello World!");\n`
+ ` const sql_query = session.prepare('SELECT ?');\n`
+ ` const query_result = sql_query.bind(arg1).execute().fetchOne();\n`
+ ` arg2 = query_result[0];\n`
+ `$$;\n`
+ `CALL\`${schemaName}\`.\`${name}\`(42, @out);\n`
+ `SELECT @out;`;
break;
}
case "msg.editRoutine": {
sql = placeHolder;
break;
}
default:
}
const provider = this.#host.currentProvider ?? this.#host.newProvider;
if (provider) {
this.createNewScriptEditor(
provider, scriptName, sql, "mysql", connectionId,
);
}
}
/**
* Triggered on authentication, which means existing connections are no longer valid.
*/
public async refreshConnectionTree(): Promise<void> {
await this.#connectionsProvider.closeAllConnections();
this.#connectionsProvider.refresh();
}
public clear(): void {
this.#openEditorsTreeDataProvider.clear();
}
/**
* Called when a new DB tree provider is opened (a new web app tab).
*
* @param provider The provider that was opened.
*/
public providerOpened(provider: DBConnectionViewProvider): void {
// Register the new provider with our data model.
this.#openDocumentsModel.openProvider(provider);
this.#openEditorsTreeDataProvider.refresh();
}
public providerClosed(provider: DBConnectionViewProvider): void {
this.#openDocumentsModel.closeProvider(provider);
this.#openScripts.delete(provider);
this.#openEditorsTreeDataProvider.refresh();
if (this.#openEditorsTreeDataProvider.clear(provider)) {
// No provider remained open. Reset the current schemas.
this.#connectionsProvider.resetCurrentSchemas();
}
}
/**
* Helper to create a unique caption for a new provider.
*
* @returns The new caption.
*/
public generateNewProviderCaption(): string {
return this.#openDocumentsModel.createUniqueCaption();
}
public providerStateChanged(provider: DBConnectionViewProvider, active: boolean): void {
this.#connectionsProvider.providerStateChanged(provider, active);
}
private createNewDbObject = async (backend: ShellInterfaceSqlEditor,
entry: ICdmTableEntry | ICdmViewEntry | ICdmRoutineEntry): Promise<IMrsDbObjectData> => {
const dbObject: IMrsDbObjectData = {
comments: "",
crudOperations: (entry.type === CdmEntityType.StoredProcedure) ? ["UPDATE"] : ["READ"],
crudOperationFormat: "FEED",
dbSchemaId: "",
enabled: 1,
id: "",
name: entry.caption,
objectType: DocumentCommandHandler.#dmTypeToMrsType.get(entry.type)!,
requestPath: `/${convertSnakeToCamelCase(entry.caption)}`,
requiresAuth: 1,
rowUserOwnershipEnforced: 0,
serviceId: "",
autoDetectMediaType: 0,
};
const services = await backend.mrs.listServices();
let service;
if (services.length === 1) {
service = services[0];
} else if (services.length > 1) {
// Lookup default service
service = services.find((service) => {
return service.isCurrent;
});
if (!service) {
// No default connection set. Show a picker.
const items = services.map((s) => {
return s.urlContextRoot;
});
const name = await window.showQuickPick(items, {
title: "Select a connection for SQL execution",
matchOnDescription: true,
placeHolder: "Type the name of an existing DB connection",
});
if (name) {
service = services.find((candidate) => {
return candidate.urlContextRoot === name;
});
}
}
}
if (service) {
const schemas = await backend.mrs.listSchemas(service.id);
const schema = schemas.find((schema) => {
return schema.name === entry.schema;
});
// Check if the DbObject's schema is already exposed as an MRS schema
dbObject.schemaName = entry.schema;
if (schema) {
dbObject.dbSchemaId = schema.id;
} else {
const answer = await ui.showInformationMessage(
`The database schema ${entry.schema} has not been added to the REST Service. Do you want to add ` +
"the schema now?", {}, "Yes", "No");
if (answer === "Yes") {
dbObject.dbSchemaId = await backend.mrs.addSchema(service.id, entry.schema, 1,
`/${convertSnakeToCamelCase(entry.schema)}`, false, null, null, undefined);
void commands.executeCommand("msg.refreshConnections");
showMessageWithTimeout(`The MRS schema ${entry.schema} has been added successfully.`, 5000);
} else {
throw new Error("Operation cancelled.");
}
}
} else {
if (services.length === 0) {
throw new Error("Please create a REST Service before adding DB Objects.");
} else {
throw new Error("No REST Service selected.");
}
}
return dbObject;
};
private connectedToUrl = (url?: URL): Promise<boolean> => {
this.#isConnected = url !== undefined;
if (this.#displayDbConnectionOverviewWhenConnected) {
this.#displayDbConnectionOverviewWhenConnected = false;
void commands.executeCommand("msg.openDBBrowser");
}
return Promise.resolve(true);
};
/**
* Triggered from CodeBlocks when an embedded query must be executed.
*
* @param details The request to send to the app.
*
* @returns A promise returning a flag whether the task was successfully executed or not.
*/
private executeCodeBlock = (details: ICodeBlockExecutionOptions): Promise<boolean> => {
const provider = this.#host.currentProvider;
if (provider) {
return provider.runCode(details.connectionId, {
linkId: details.linkId,
code: details.query,
language: "mysql",
});
}
return Promise.resolve(false);
};
private editorLoadScript = (details: IScriptRequest): Promise<boolean> => {
// The user has to select a target file.
const filters: { [key: string]: string[]; } = {};
switch (details.language) {
case "mysql": {
filters.SQL = ["mysql", "sql"];
break;
}
case "sql": {
filters.SQL = ["sql"];
break;
}
case "typescript": {
filters.TypeScript = ["ts"];
break;
}
case "javascript": {
filters.JavaScript = ["js"];
break;
}
default:
}
void window.showOpenDialog({
title: "Load Script File",
filters,
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
}).then((list: Uri[]) => {
if (list.length > 0) {
void workspace.fs.readFile(list[0]).then((content) => {
const provider = this.#host.currentProvider;
if (provider) {
const scripts = this.#openScripts.get(provider);
if (scripts) {
scripts.set(details.id, list[0]);
const newName = basename(list[0].fsPath);
void provider.renameFile({