in gui/frontend/src/modules/shell/ShellTab.tsx [351:710]
private async doExecution(command: string, context: ExecutionContext, index: number,
_params?: Array<[string, string]>): Promise<void> {
const { savedState } = this.props;
savedState.lastCommand = command;
try {
const { id = "" } = this.props;
let columns: IColumnInfo[] = [];
let resultId = uuid();
const finalResult = await savedState.backend.execute(command, undefined, (data, requestId) => {
if (!data.result) {
return;
}
const result = data.result;
// Shell response results can have many different fields. There's no other way but to test
// for each possible field to see what a response is about.
if (this.isShellShellDocumentData(result)) {
// Document data must be handled first, as that includes an info field,
// like the simple result.
if (result.hasData) {
const documentString = result.documents.length === 1 ? "document" : "documents";
const status = {
type: MessageType.Info,
text: `${result.documents.length} ${documentString} in set ` +
`(${result.executionTime})`,
};
if (result.warningsCount > 0) {
status.type = MessageType.Warning;
status.text += `, ${result.warningsCount} ` +
`${result.warningsCount === 1 ? "warning" : "warnings"}`;
}
const text: ITextResultEntry[] = [{
type: MessageType.Info,
index,
content: JSON.stringify(result.documents, undefined, "\t"),
language: "json",
}];
result.warnings.forEach((warning) => {
text.push({
type: MessageType.Warning,
index,
content: `\n${warning.message}`,
});
});
this.addTimedResult(context, {
type: "text",
text,
executionInfo: status,
}, resultId);
} else {
// No data was returned. Use the info field for the status message then.
this.addTimedResult(context, {
type: "text",
text: [{
type: MessageType.Info,
index,
content: result.info,
language: "ansi",
}],
executionInfo: { text: "" },
}, resultId);
}
} else if (this.isShellShellColumnsMetaData(result)) {
const rawColumns = Object.values(result).map((value) => {
return {
name: unquote(value.Name),
type: value.Type,
length: value.Length,
};
});
columns.push(...generateColumnInfo(
context.language === "mysql" ? DBType.MySQL : DBType.Sqlite, rawColumns, true));
} else if (this.isShellShellRowData(result)) {
// If we have column info at this point then we got SQL mode results (show the result grid).
// Otherwise display the result as JSON text.
if (columns.length === 0) {
const resultText = result.warningsCount > 0 ?
`finished with warnings (${result.warningsCount})` : "OK";
let info = `Query ${resultText}, ${result.affectedItemsCount || result.rows.length} ` +
`rows affected`;
if (result.executionTime) {
info += ` (${result.executionTime})`;
}
// If we have data show it as result otherwise only the execution info.
if (result.rows.length > 0) {
const content = JSON.stringify(result.rows, undefined, "\t") + "\n";
this.addTimedResult(context, {
type: "text",
text: [{
type: MessageType.Info,
index,
content,
language: "json",
}],
executionInfo: { type: MessageType.Response, text: info },
}, resultId);
} else {
this.addTimedResult(context, {
type: "text",
executionInfo: { type: MessageType.Info, text: info },
}, resultId);
}
} else {
const rowString = result.rows.length === 1 ? "row" : "rows";
const status = {
type: MessageType.Info,
text: `${result.rows.length} ${rowString} in set (${result.executionTime})`,
};
if (result.warningsCount > 0) {
status.type = MessageType.Warning;
status.text += `, ${result.warningsCount} ` +
`${result.warningsCount === 1 ? "warning" : "warnings"}`;
}
// Flatten nested objects + arrays.
result.rows.forEach((value) => {
flattenObject(value as IDictionary);
});
const rows = convertRows(columns, result.rows);
void ApplicationDB.db.add("shellModuleResultData", {
tabId: id,
resultId,
rows,
columns,
executionInfo: status,
index,
});
this.addTimedResult(context, {
type: "resultSetRows",
rows,
columns,
currentPage: 0,
executionInfo: status,
}, resultId);
}
// We got the final data. Start a new result.
resultId = uuid();
columns = [];
} else if (this.isShellShellData(result)) {
// Unspecified shell data (no documents, no rows). Just print the info as status, for now.
this.addTimedResult(context, {
type: "text",
text: [{
type: MessageType.Info,
index,
content: result.info,
language: "ansi",
}],
executionInfo: { text: "" },
}, resultId);
} else if (this.isShellObjectListResult(result)) {
let text = "[\n";
result.forEach((value) => {
text += "\t<" + value.class;
if (value.name) {
text += ":" + value.name;
}
text += ">\n";
});
text += "]";
this.addTimedResult(context, {
type: "text",
text: [{
type: MessageType.Info,
index,
content: text,
language: "xml",
}],
}, resultId);
} else if (this.isShellSimpleResult(result)) {
if (result.error) {
// Errors can be a string or an object with a string.
const text = typeof result.error === "string" ? result.error : result.error.message;
this.addTimedResult(context, {
type: "text",
text: [{
type: MessageType.Error,
index,
content: text,
language: "ansi",
}],
executionInfo: { type: MessageType.Error, text: "" },
}, resultId);
} else if (result.warning) {
// Errors can be a string or an object with a string.
this.addTimedResult(context, {
type: "text",
text: [{
type: MessageType.Info,
index,
content: result.warning,
language: "ansi",
}],
executionInfo: { type: MessageType.Warning, text: "" },
}, resultId);
} else {
// See if a new session is going to be established. That's our sign to
// remove a previously stored password.
// If the scheme is explicit, the message will include the type of session that is being
// created ("Classic" or "X"). We can be open-ended and look for any kind of session, even
// those types that do not yet exist.
const newSessionPattern = /^Creating (a|an)([\s\w]+)? session to/;
if (result.info && newSessionPattern.test(result.info)) {
this.closeDbSession(result.info);
}
const content = (result.info ?? result.note ?? result.status)!;
this.addTimedResult(context, {
type: "text",
text: [{
type: MessageType.Info,
index,
content,
language: "ansi",
}],
executionInfo: { type: MessageType.Info, text: "" },
}, resultId);
}
// We got a full result. Start a new one.
resultId = uuid();
columns = [];
} else if (this.isShellValueResult(result)) {
this.addTimedResult(context, {
type: "text",
text: [{
type: MessageType.Info,
index,
content: String(result.value),
language: "ansi",
}],
}, resultId);
} else {
// XXX: The password requisition is no longer available. Find a different way to capture
// the password.
//requisitions.register("acceptPassword", this.acceptPassword);
if (!ShellPromptHandler.handleShellPrompt(result, requestId, savedState.backend)) {
if (this.isShellObjectResult(result)) {
let text = "<" + result.class;
if (result.name) {
text += ":" + result.name;
}
text += ">";
this.addTimedResult(context, {
type: "text",
text: [{
type: MessageType.Info,
index,
content: text,
language: "xml",
}],
}, resultId);
} else {
// If no specialized result then print as is.
const executionInfo: IStatusInfo = {
text: result ? "" : JSON.stringify(result, undefined, "\t"),
};
const text = !result ? [] : [{
type: MessageType.Info,
index,
content: JSON.stringify(result, undefined, "\t"),
language: "json" as ResultTextLanguage,
}];
this.addTimedResult(context, {
type: "text",
text,
executionInfo,
}, resultId);
}
}
}
});
// Handling the final response here.
if (finalResult && this.hasPromptDescriptor(finalResult)) {
// A descriptor change occurs when any of the following change types occur:
// 1) Changes involving an active session update:
// 1.1) A new active shell session was established
// 1.2) The active shell session got disconnected
// 2) Changes not involving changes on the active session:
// 2.1) The active schema changed
// 2.2) The Shell Console mode changed
const hadOpenSession = savedState.dbSession !== undefined;
const needsOpenSession = (finalResult.promptDescriptor?.host !== undefined)
|| (finalResult.promptDescriptor?.socket !== undefined);
const existing = savedState.promptDescriptor;
let newSessionRequired = (!hadOpenSession && needsOpenSession);
if (!newSessionRequired) {
newSessionRequired = (existing?.user !== finalResult.promptDescriptor?.user)
|| (existing?.host !== finalResult.promptDescriptor?.host)
|| (existing?.port !== finalResult.promptDescriptor?.port)
|| (existing?.socket !== finalResult.promptDescriptor?.socket)
|| (existing?.ssl !== finalResult.promptDescriptor?.ssl)
|| (existing?.session !== finalResult.promptDescriptor?.session);
}
// Prompt needs to be updated the first time a descriptor comes in.
let refreshRequired = !savedState.promptDescriptor
|| newSessionRequired
|| (hadOpenSession !== needsOpenSession);
if (!refreshRequired) {
refreshRequired = (existing?.isProduction !== finalResult.promptDescriptor?.isProduction)
|| (existing?.mode !== finalResult.promptDescriptor?.mode)
|| (existing?.schema !== finalResult.promptDescriptor?.schema);
}
savedState.promptDescriptor = finalResult.promptDescriptor;
if (hadOpenSession && !needsOpenSession) {
// 1.2) There was an active session and got closed
this.closeDbSession("");
void requisitions.execute("updateShellPrompt", finalResult);
} else if (newSessionRequired) {
// A new session will be created, however if password is not available, it attempts to
// get it from the last executed command (common case when password is given in the
// connection string)
if (savedState.lastPassword === undefined) {
savedState.lastPassword = this.getPasswordFromLastCommand();
}
// Only trigger the creation of the Session if the password was defined (even if empty)
// otherwise the start session will be requiring shell prompt handling
if (savedState.lastPassword !== undefined) {
// 1.1) The active session got created or updated
await this.openDbSession();
}
await requisitions.execute("updateShellPrompt", finalResult);
} else if (refreshRequired) {
// 2) Changes not related to a session update were detected
await requisitions.execute("updateShellPrompt", finalResult);
}
}
} catch (reason) {
const message = convertErrorToString(reason);
void ui.showErrorMessage(`Shell Execution Error: ${message}`, {});
throw reason;
}
}