* OE shows all connections

* OE changes

* added node for adding connections

* remove dead code

* fix test

* change connection for new query from OE

* fixed OE loading from editor and command together

* cleanup after node removal

* fix icon paths for diff OS

* changed expansion to deferred as well

* added new UX for password protected servers

* better UX for server errors and save password

* fix context menu action order

* match profile name in node when making a node from new query

* new query and add working fine

* added icon for add connection node

* fix tests

* fix tests

* Scripting (#1304)

* scripting works

* scripting complete

* removed dead code

* fixed scripting

* fix password issue in saved connections
This commit is contained in:
Aditya Bist 2019-09-24 00:57:33 -07:00 коммит произвёл GitHub
Родитель f6354613b4
Коммит b5a3c1ac72
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
24 изменённых файлов: 990 добавлений и 145 удалений

Просмотреть файл

@ -368,6 +368,15 @@
<trans-unit id="queryOutputHostTitle">
<source xml:lang="en">Query Output Document Host</source>
</trans-unit>
<trans-unit id="msgAddConnection">
<source xml:lang="en">Add Connection</source>
</trans-unit>
<trans-unit id="msgSignIn">
<source xml:lang="en">Sign In</source>
</trans-unit>
<trans-unit id="msgConnect">
<source xml:lang="en">Connect</source>
</trans-unit>
</body>
</file>
</xliff>

Просмотреть файл

@ -161,7 +161,7 @@
"activitybar": [
{
"id": "objectExplorer",
"title": "Object Explorer",
"title": "SQL Server",
"icon": "./images/server_page_inverse.svg"
}
]
@ -216,23 +216,23 @@
"view/item/context": [
{
"command": "extension.objectExplorerNewQuery",
"when": "viewItem == Server"
},
{
"command": "extension.objectExplorerNewQuery",
"when": "viewItem == Database"
},
{
"command": "extension.objectExplorerNewQuery",
"when": "viewItem == Table"
"when": "viewItem =~ /^(disconnectedServer|Server|Database|Table)$/",
"group": "MS_SQL@1"
},
{
"command": "extension.removeObjectExplorerNode",
"when": "viewItem == Server"
"when": "viewItem =~ /^(disconnectedServer|Server)$/",
"group": "MS_SQL@3"
},
{
"command": "extension.refreshObjectExplorerNode",
"when": "view == objectExplorer"
"when": "view == objectExplorer",
"group": "MS_SQL@2"
},
{
"command": "extension.disconnectObjectExplorerNode",
"when": "viewItem == Server",
"group": "MS_SQL@4"
},
{
"command": "extension.scriptSelect",
@ -343,6 +343,11 @@
"title": "%extension.refreshObjectExplorerNode%",
"category": "MS SQL"
},
{
"command": "extension.disconnectObjectExplorerNode",
"title": "%extension.disconnect%",
"category": "MS SQL"
},
{
"command": "extension.scriptSelect",
"title": "%extension.scriptSelect%",

Просмотреть файл

@ -13,6 +13,8 @@ export const connectionApplicationName = 'vscode-mssql';
export const outputChannelName = 'MSSQL';
export const connectionConfigFilename = 'settings.json';
export const connectionsArrayName = 'connections';
export const disconnectedServerLabel = 'disconnectedServer';
export const serverLabel = 'Server';
export const cmdRunQuery = 'extension.runQuery';
export const cmdRunCurrentStatement = 'extension.runCurrentStatement';
export const cmdCancelQuery = 'extension.cancelQuery';
@ -29,6 +31,9 @@ export const cmdAddObjectExplorer = 'extension.addObjectExplorer';
export const cmdObjectExplorerNewQuery = 'extension.objectExplorerNewQuery';
export const cmdRemoveObjectExplorerNode = 'extension.removeObjectExplorerNode';
export const cmdRefreshObjectExplorerNode = 'extension.refreshObjectExplorerNode';
export const cmdDisconnectObjectExplorerNode = 'extension.disconnectObjectExplorerNode';
export const cmdObjectExplorerNodeSignIn = 'extension.objectExplorerNodeSignIn';
export const cmdConnectObjectExplorerNode = 'extension.connectObjectExplorerNode';
export const cmdOpenObjectExplorerCommand = 'workbench.view.extension.objectExplorer';
export const cmdScriptSelect = 'extension.scriptSelect';
export const cmdToggleSqlCmd = 'extension.toggleSqlCmd';
@ -113,4 +118,3 @@ export const serviceNotCompatibleError = 'Client is not compatible with the serv
export const sqlToolsServiceConfigKey = 'service';
export const v1SqlToolsServiceConfigKey = 'v1Service';
export const scriptSelectText = 'SELECT TOP (1000) * FROM ';
export const useDatabaseText = 'USE ';

Просмотреть файл

@ -85,7 +85,8 @@ export class ConnectionInfo {
export default class ConnectionManager {
private _statusView: StatusView;
private _connections: { [fileUri: string]: ConnectionInfo };
private _objectExplorerSessions: { [sessionId: string]: ConnectionInfo };
private _connectionCredentialsToServerInfoMap:
Map<Interfaces.IConnectionCredentials, ConnectionContracts.ServerInfo>;
constructor(context: vscode.ExtensionContext,
statusView: StatusView,
@ -96,7 +97,8 @@ export default class ConnectionManager {
private _connectionUI?: ConnectionUI) {
this._statusView = statusView;
this._connections = {};
this._objectExplorerSessions = {};
this._connectionCredentialsToServerInfoMap =
new Map<Interfaces.IConnectionCredentials, ConnectionContracts.ServerInfo>();
if (!this.client) {
this.client = SqlToolsServerClient.instance;
@ -267,6 +269,7 @@ export default class ConnectionManager {
let connection = self.getConnectionInfo(fileUri);
connection.serviceTimer.end();
connection.connecting = false;
this._connectionCredentialsToServerInfoMap.set(connection.credentials, result.serverInfo);
let mruConnection: Interfaces.IConnectionCredentials = <any>{};
@ -388,7 +391,7 @@ export default class ConnectionManager {
return this.connectionStore.clearRecentlyUsed();
}
// choose database to use on current server
// choose database to use on current server from UI
public onChooseDatabase(): Promise<boolean> {
const self = this;
const fileUri = this.vscodeWrapper.activeTextEditorUri;
@ -438,6 +441,27 @@ export default class ConnectionManager {
});
}
public async changeDatabase(newDatabaseCredentials: Interfaces.IConnectionCredentials): Promise<boolean> {
const self = this;
const fileUri = this.vscodeWrapper.activeTextEditorUri;
return new Promise<boolean>(async (resolve, reject) => {
if (!self.isConnected(fileUri)) {
self.vscodeWrapper.showWarningMessage(LocalizedConstants.msgChooseDatabaseNotConnected);
resolve(false);
return;
}
await self.disconnect(fileUri);
await self.connect(fileUri, newDatabaseCredentials);
Telemetry.sendTelemetryEvent('UseDatabase');
self.vscodeWrapper.logToOutputChannel(
Utils.formatString(
LocalizedConstants.msgChangedDatabase,
newDatabaseCredentials.database,
newDatabaseCredentials.server, fileUri));
return true;
});
}
public onChooseLanguageFlavor(isSqlCmd: boolean = false): Promise<boolean> {
const fileUri = this._vscodeWrapper.activeTextEditorUri;
if (fileUri && this._vscodeWrapper.isEditingSqlFile) {
@ -523,7 +547,7 @@ export default class ConnectionManager {
self.connect(fileUri, connectionCreds)
.then(result => {
self.handleConnectionResult(result, fileUri, connectionCreds).then(() => {
resolve(true);
resolve(connectionCreds);
});
});
});
@ -533,6 +557,16 @@ export default class ConnectionManager {
});
}
/**
* Get the server info for a connection
* @param connectionCreds
*/
public getServerInfo(connectionCredentials: Interfaces.IConnectionCredentials): ConnectionContracts.ServerInfo {
if (this._connectionCredentialsToServerInfoMap.has(connectionCredentials)) {
return this._connectionCredentialsToServerInfoMap.get(connectionCredentials);
}
}
/**
* Verifies the connection result. If connection failed because of invalid credentials,
* tries to connect again by asking user for different credentials
@ -566,22 +600,22 @@ export default class ConnectionManager {
// let users pick from a picklist of connections
public onNewConnection(objectExplorerSessionId?: string): Promise<boolean> {
public onNewConnection(objectExplorerSessionId?: string): Promise<Interfaces.IConnectionCredentials> {
const self = this;
const fileUri = objectExplorerSessionId ? objectExplorerSessionId : this.vscodeWrapper.activeTextEditorUri;
return new Promise<boolean>((resolve, reject) => {
return new Promise<Interfaces.IConnectionCredentials>((resolve, reject) => {
if (!fileUri) {
// A text document needs to be open before we can connect
self.vscodeWrapper.showWarningMessage(LocalizedConstants.msgOpenSqlFile);
resolve(false);
resolve(undefined);
return;
} else if (!self.vscodeWrapper.isEditingSqlFile) {
self.connectionUI.promptToChangeLanguageMode().then( result => {
self.connectionUI.promptToChangeLanguageMode().then(result => {
if (result) {
self.showConnectionsAndConnect(resolve, reject, fileUri);
} else {
resolve(false);
resolve(undefined);
}
});
return;

Просмотреть файл

@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as os from 'os';
import * as events from 'events';
import vscode = require('vscode');
import Constants = require('../constants/constants');
@ -24,8 +23,12 @@ import { ISelectionData, IConnectionProfile } from './../models/interfaces';
import * as path from 'path';
import fs = require('fs');
import { ObjectExplorerProvider } from '../objectExplorer/objectExplorerProvider';
import { ScriptingService } from '../scripting/scriptingService';
import { escapeCharacters } from '../utils/escapeCharacters';
import { TreeNodeInfo } from '../objectExplorer/treeNodeInfo';
import { AccountSignInTreeNode } from '../objectExplorer/accountSignInTreeNode';
import { Deferred } from '../protocol';
import { ConnectTreeNode } from '../objectExplorer/connectTreeNode';
/**
* The main controller class that initializes the extension
@ -45,6 +48,7 @@ export default class MainController implements vscode.Disposable {
private _lastOpenedTimer: Utils.Timer;
private _untitledSqlDocumentService: UntitledSqlDocumentService;
private _objectExplorerProvider: ObjectExplorerProvider;
private _scriptingService: ScriptingService;
/**
* The main controller constructor
@ -144,38 +148,82 @@ export default class MainController implements vscode.Disposable {
if (!self._objectExplorerProvider.objectExplorerExists) {
self._objectExplorerProvider.objectExplorerExists = true;
}
return self._objectExplorerProvider.createSession();
let promise = new Deferred<TreeNodeInfo>();
await self._objectExplorerProvider.createSession(promise);
return promise.then(() => {
this._objectExplorerProvider.refresh(undefined);
});
});
this._context.subscriptions.push(vscode.commands.registerCommand(Constants.cmdObjectExplorerNewQuery, async (treeNodeInfo) => {
const databaseName = `${escapeCharacters(self.getDatabaseName(treeNodeInfo))}`;
const useStatement = Constants.useDatabaseText + databaseName + os.EOL;
self.runAndLogErrors(self.onNewQuery(treeNodeInfo.sessionId, useStatement), 'onNewQueryOE');
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdObjectExplorerNewQuery, async (treeNodeInfo: TreeNodeInfo) => {
const connectionCredentials = treeNodeInfo.connectionCredentials;
if (connectionCredentials) {
const databaseName = `${escapeCharacters(self.getDatabaseName(treeNodeInfo))}`;
if (databaseName !== connectionCredentials.database) {
connectionCredentials.database = databaseName;
}
}
await self.onNewQuery(treeNodeInfo);
await this._connectionMgr.changeDatabase(connectionCredentials);
}));
this.registerCommand(Constants.cmdRemoveObjectExplorerNode);
this._event.on(Constants.cmdRemoveObjectExplorerNode, async () => {
let connProfile = <IConnectionProfile>this._objectExplorerProvider.getConnectionCredentials(
this._objectExplorerProvider.currentNode.sessionId);
await this._connectionMgr.connectionStore.removeProfile(connProfile, false);
await this._objectExplorerProvider.removeObjectExplorerNode(this._objectExplorerProvider.currentNode);
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdRemoveObjectExplorerNode, async (treeNodeInfo: TreeNodeInfo) => {
await this._objectExplorerProvider.removeObjectExplorerNode(treeNodeInfo);
let profile = <IConnectionProfile>treeNodeInfo.connectionCredentials;
await this._connectionMgr.connectionStore.removeProfile(profile, false);
return this._objectExplorerProvider.refresh(undefined);
});
}));
this.registerCommand(Constants.cmdRefreshObjectExplorerNode);
this._event.on(Constants.cmdRefreshObjectExplorerNode, () => {
return this._objectExplorerProvider.refreshNode(this._objectExplorerProvider.currentNode);
});
this._context.subscriptions.push(vscode.commands.registerCommand(Constants.cmdScriptSelect, async (treeNodeInfo) => {
const objectNames = treeNodeInfo.label.split('.');
const tableName = `[${escapeCharacters(objectNames[0])}].[${escapeCharacters(objectNames[1])}]`;
const databaseName = `[${escapeCharacters(self.getDatabaseName(treeNodeInfo))}].`;
const selectStatement = Constants.scriptSelectText + databaseName + tableName;
self.onNewQuery(treeNodeInfo.sessionId, selectStatement).then((result) => {
if (result) {
self.onRunQuery();
}
});
// initiate the scripting service
this._scriptingService = new ScriptingService(this._connectionMgr, this._vscodeWrapper);
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdScriptSelect, async (node: TreeNodeInfo) => {
const uri = await this._untitledSqlDocumentService.newQuery();
if (!this.connectionManager.isConnected(uri.toString())) {
const connectionCreds = node.connectionCredentials;
const databaseName = `${escapeCharacters(self.getDatabaseName(node))}`;
node.connectionCredentials.database = databaseName;
this._statusview.languageFlavorChanged(uri.toString(), Constants.mssqlProviderName);
this._statusview.sqlCmdModeChanged(uri.toString(), false);
await this.connectionManager.connect(uri.toString(), connectionCreds);
const selectStatement = await this._scriptingService.scriptSelect(node, uri.toString());
let editor = this._vscodeWrapper.activeTextEditor;
editor.edit(editBuilder => {
editBuilder.replace(editor.selection, selectStatement);
}).then(() => this.onRunQuery());
}
}));
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdObjectExplorerNodeSignIn, async (node: AccountSignInTreeNode) => {
this._objectExplorerProvider.signInNodeServer(node.parentNode);
return this._objectExplorerProvider.refresh(undefined);
}));
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdConnectObjectExplorerNode, async (node: ConnectTreeNode) => {
let promise = new Deferred<TreeNodeInfo>();
await self._objectExplorerProvider.createSession(promise, node.parentNode.connectionCredentials);
return promise.then(() => {
this._objectExplorerProvider.refresh(undefined);
});
}));
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdDisconnectObjectExplorerNode, async (node: TreeNodeInfo) => {
await this._objectExplorerProvider.removeObjectExplorerNode(node, true);
}));
// Add handlers for VS Code generated commands
this._vscodeWrapper.onDidCloseTextDocument(params => this.onDidCloseTextDocument(params));
this._vscodeWrapper.onDidOpenTextDocument(params => this.onDidOpenTextDocument(params));
@ -359,7 +407,13 @@ export default class MainController implements vscode.Disposable {
*/
public onNewConnection(): Promise<boolean> {
if (this.canRunCommand() && this.validateTextDocumentHasFocus()) {
return this._connectionMgr.onNewConnection();
this._connectionMgr.onNewConnection().then((result) => {
if (result) {
this._objectExplorerProvider.objectExplorerExists = false;
this._objectExplorerProvider.refresh(undefined);
return true;
}
});
}
return Promise.resolve(false);
}
@ -610,20 +664,30 @@ export default class MainController implements vscode.Disposable {
/**
* Opens a new query and creates new connection
*/
public async onNewQuery(sessionId?: string, content?: string): Promise<boolean> {
public async onNewQuery(node?: TreeNodeInfo, content?: string): Promise<boolean> {
if (this.canRunCommand()) {
if (sessionId) {
// from the object explorer context menu
if (node) {
const uri = await this._untitledSqlDocumentService.newQuery(content);
// connect to the node if the command came from the context
if (!this.connectionManager.isConnected(sessionId)) {
const connectionCreds = this._objectExplorerProvider.getConnectionCredentials(sessionId);
if (!this.connectionManager.isConnected(uri.toString())) {
const connectionCreds = node.connectionCredentials;
this._statusview.languageFlavorChanged(uri.toString(), Constants.mssqlProviderName);
return this.connectionManager.connect(uri.toString(), connectionCreds);
this._statusview.sqlCmdModeChanged(uri.toString(), false);
let result = await this.connectionManager.connect(uri.toString(), connectionCreds);
return Promise.resolve(result);
}
} else {
let uri = await this._untitledSqlDocumentService.newQuery();
this._statusview.sqlCmdModeChanged(uri.toString(), false);
return this._connectionMgr.onNewConnection();
// new query command
const uri = await this._untitledSqlDocumentService.newQuery();
this._connectionMgr.onNewConnection().then(async (result) => {
// initiate a new OE with same connection
if (result) {
this._objectExplorerProvider.refresh(undefined);
}
this._statusview.sqlCmdModeChanged(uri.toString(), false);
return Promise.resolve(true);
});
}
}
return Promise.resolve(false);

Просмотреть файл

@ -257,7 +257,7 @@ export class ConnectionCredentials implements IConnectionCredentials {
// Prompt for password if this is a password based credential and the password for the profile was empty
// and not explicitly set as empty. If it was explicitly set as empty, only prompt if pw not saved
private static shouldPromptForPassword(credentials: IConnectionCredentials): boolean {
public static shouldPromptForPassword(credentials: IConnectionCredentials): boolean {
let isSavedEmptyPassword: boolean = (<IConnectionProfile>credentials).emptyPasswordInput
&& (<IConnectionProfile>credentials).savePassword;

Просмотреть файл

@ -153,6 +153,24 @@ export class ConnectionStore {
});
}
/**
* Lookup credential store
* @param connectionCredentials Connection credentials of profile for password lookup
*/
public async lookupPassword(connectionCredentials: IConnectionCredentials): Promise<string> {
const databaseName = connectionCredentials.database === '' ? 'master' :
connectionCredentials.database;
const credentialId = ConnectionStore.formatCredentialId(
connectionCredentials.server, databaseName,
connectionCredentials.user, ConnectionStore.CRED_MRU_USER);
const savedCredential = await this._credentialStore.readCredential(credentialId);
if (savedCredential && savedCredential.password) {
return savedCredential.password;
} else {
return undefined;
}
}
/**
* public for testing purposes. Validates whether a password should be looked up from the credential store or not
*

Просмотреть файл

@ -253,4 +253,4 @@ export class ListDatabasesResult {
public databaseNames: Array<string>;
}
// ------------------------------- </ List Databases Request > --------------------------------------
// ------------------------------- </ List Databases Request > --------------------------------------

Просмотреть файл

@ -0,0 +1,303 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { RequestType } from 'vscode-jsonrpc';
export interface ConnectionInfo {
options: { [name: string]: any };
}
export enum ScriptOperation {
Select = 0,
Create = 1,
Insert = 2,
Update = 3,
Delete = 4,
Execute = 5,
Alter = 6
}
export interface ScriptOptions {
/**
* Generate ANSI padding statements
*/
scriptANSIPadding?: boolean;
/**
* Append the generated script to a file
*/
appendToFile?: boolean;
/**
* Continue to script if an error occurs. Otherwise, stop.
*/
continueScriptingOnError?: boolean;
/**
* Convert user-defined data types to base types.
*/
convertUDDTToBaseType?: boolean;
/**
* Generate script for dependent objects for each object scripted.
*/
generateScriptForDependentObjects?: boolean;
/**
* Include descriptive headers for each object generated.
*/
includeDescriptiveHeaders?: boolean;
/**
* Check that an object with the given name exists before dropping or altering or that an object with the given name does not exist before creating.
*/
includeIfNotExists?: boolean;
/**
* Script options to set vardecimal storage format.
*/
includeVarDecimal?: boolean;
/**
* Include system generated constraint names to enforce declarative referential integrity.
*/
scriptDRIIncludeSystemNames?: boolean;
/**
* Include statements in the script that are not supported on the specified SQL Server database engine type.
*/
includeUnsupportedStatements?: boolean;
/**
* Prefix object names with the object schema.
*/
schemaQualify?: boolean;
/**
* Script options to set bindings option.
*/
bindings?: boolean;
/**
* Script the objects that use collation.
*/
collation?: boolean;
/**
* Script the default values.
*/
default?: boolean;
/**
* Script Object CREATE/DROP statements.
*/
scriptCreateDrop: string;
/**
* Script the Extended Properties for each object scripted.
*/
scriptExtendedProperties?: boolean;
/**
* Script only features compatible with the specified version of SQL Server.
*/
scriptCompatibilityOption: string;
/**
* Script only features compatible with the specified SQL Server database engine type.
*/
targetDatabaseEngineType: string;
/**
* Script only features compatible with the specified SQL Server database engine edition.
*/
targetDatabaseEngineEdition: string;
/**
* Script all logins available on the server. Passwords will not be scripted.
*/
scriptLogins?: boolean;
/**
* Generate object-level permissions.
*/
scriptObjectLevelPermissions?: boolean;
/**
* Script owner for the objects.
*/
scriptOwner?: boolean;
/**
* Script statistics, and optionally include histograms, for each selected table or view.
*/
scriptStatistics: string;
/**
* Generate USE DATABASE statement.
*/
scripUseDatabase?: boolean;
/**
* Generate script that contains schema only or schema and azdata.
*/
typeOfDataToScript: string;
/**
* Scripts the change tracking information.
*/
scriptChangeTracking?: boolean;
/**
* Script the check constraints for each table or view scripted.
*/
scriptCheckConstraints?: boolean;
/**
* Scripts the data compression information.
*/
scriptDataCompressionOptions?: boolean;
/**
* Script the foreign keys for each table scripted.
*/
scriptForeignKeys?: boolean;
/**
* Script the full-text indexes for each table or indexed view scripted.
*/
scriptFullTextIndexes?: boolean;
/**
* Script the indexes (including XML and clustered indexes) for each table or indexed view scripted.
*/
scriptIndexes?: boolean;
/**
* Script the primary keys for each table or view scripted
*/
scriptPrimaryKeys?: boolean;
/**
* Script the triggers for each table or view scripted
*/
scriptTriggers?: boolean;
/**
* Script the unique keys for each table or view scripted.
*/
uniqueKeys?: boolean;
}
export interface ScriptingObject {
/**
* The database object type
*/
type: string;
/**
* The schema of the database object
*/
schema: string;
/**
* The database object name
*/
name: string;
}
export interface ScriptingParams {
/**
* File path used when writing out the script.
*/
filePath: string;
/**
* Whether scripting to a single file or file per object.
*/
scriptDestination: string;
/**
* Connection string of the target database the scripting operation will run against.
*/
connectionString: string;
/**
* A list of scripting objects to script
*/
scriptingObjects: ScriptingObject[];
/**
* A list of scripting object which specify the include criteria of objects to script.
*/
includeObjectCriteria: ScriptingObject[];
/**
* A list of scripting object which specify the exclude criteria of objects to not script.
*/
excludeObjectCriteria: ScriptingObject[];
/**
* A list of schema name of objects to script.
*/
includeSchemas: string[];
/**
* A list of schema name of objects to not script.
*/
excludeSchemas: string[];
/**
* A list of type name of objects to script.
*/
includeTypes: string[];
/**
* A list of type name of objects to not script.
*/
excludeTypes: string[];
/**
* Scripting options for the ScriptingParams
*/
scriptOptions: ScriptOptions;
/**
* Connection details for the ScriptingParams
*/
connectionDetails: ConnectionInfo;
/**
* Owner URI of the connection
*/
ownerURI: string;
/**
* Whether the scripting operation is for
* select script statements
*/
selectScript: boolean;
/**
* Operation associated with the script request
*/
operation: ScriptOperation;
}
export interface ScriptingResult {
operationId: string;
script: string;
}
// ------------------------------- < Scripting Request > ----------------------------------------------
export namespace ScriptingRequest {
/**
* Returns children of a given node as a NodeInfo array.
*/
export const type = new RequestType<ScriptingParams, ScriptingResult, void, void>('scripting/script');
}

Просмотреть файл

@ -0,0 +1,29 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as vscode from 'vscode';
import * as LocalizedConstants from '../constants/localizedConstants';
import Constants = require('../constants/constants');
import { TreeNodeInfo } from './treeNodeInfo';
export class AccountSignInTreeNode extends vscode.TreeItem {
constructor(
private _parentNode: TreeNodeInfo,
) {
super(LocalizedConstants.msgSignIn, vscode.TreeItemCollapsibleState.None);
this.command = {
title: LocalizedConstants.msgSignIn,
command: Constants.cmdObjectExplorerNodeSignIn,
arguments: [this]
};
}
public get parentNode(): TreeNodeInfo {
return this._parentNode;
}
}

Просмотреть файл

@ -0,0 +1,24 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as path from 'path';
import * as vscode from 'vscode';
import Constants = require('../constants/constants');
import * as LocalizedConstants from '../constants/localizedConstants';
import { ObjectExplorerUtils } from './objectExplorerUtils';
export class AddConnectionTreeNode extends vscode.TreeItem {
constructor() {
super(LocalizedConstants.msgAddConnection, vscode.TreeItemCollapsibleState.None);
this.command = {
title: LocalizedConstants.msgAddConnection,
command: Constants.cmdAddObjectExplorer
};
this.iconPath = {
light: path.join(ObjectExplorerUtils.rootPath, 'add.svg'),
dark: path.join(ObjectExplorerUtils.rootPath, 'add_inverse.svg')
};
}
}

Просмотреть файл

@ -0,0 +1,28 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as vscode from 'vscode';
import * as LocalizedConstants from '../constants/localizedConstants';
import Constants = require('../constants/constants');
import { TreeNodeInfo } from './treeNodeInfo';
export class ConnectTreeNode extends vscode.TreeItem {
constructor(
private _parentNode: TreeNodeInfo,
) {
super(LocalizedConstants.msgConnect, vscode.TreeItemCollapsibleState.None);
this.command = {
title: LocalizedConstants.msgConnect,
command: Constants.cmdConnectObjectExplorerNode,
arguments: [this]
};
}
public get parentNode(): TreeNodeInfo {
return this._parentNode;
}
}

Просмотреть файл

@ -8,6 +8,8 @@ import ConnectionManager from '../controllers/connectionManager';
import { ObjectExplorerService } from './objectExplorerService';
import { ConnectionCredentials } from '../models/connectionCredentials';
import { TreeNodeInfo } from './treeNodeInfo';
import { IConnectionCredentials } from '../models/interfaces';
import { Deferred } from '../protocol';
export class ObjectExplorerProvider implements vscode.TreeDataProvider<any> {
@ -29,29 +31,36 @@ export class ObjectExplorerProvider implements vscode.TreeDataProvider<any> {
return node;
}
getChildren(element?: TreeNodeInfo): Promise<vscode.TreeItem[]> {
const children = this._objectExplorerService.getChildren(element);
async getChildren(element?: TreeNodeInfo): Promise<vscode.TreeItem[]> {
const children = await this._objectExplorerService.getChildren(element);
if (children) {
return Promise.resolve(children);
return children;
}
}
async createSession(): Promise<string> {
return await this._objectExplorerService.createSession();
async createSession(promise: Deferred<TreeNodeInfo>, connectionCredentials?: IConnectionCredentials): Promise<void> {
return this._objectExplorerService.createSession(promise, connectionCredentials);
}
public getConnectionCredentials(sessionId: string): ConnectionCredentials {
return this._objectExplorerService.getConnectionCredentials(sessionId);
if (sessionId) {
return this._objectExplorerService.getConnectionCredentials(sessionId);
}
return undefined;
}
public removeObjectExplorerNode(node: TreeNodeInfo): Promise<void> {
return this._objectExplorerService.removeObjectExplorerNode(node);
public removeObjectExplorerNode(node: TreeNodeInfo, isDisconnect: boolean = false): Promise<void> {
return this._objectExplorerService.removeObjectExplorerNode(node, isDisconnect);
}
public refreshNode(node: TreeNodeInfo): Promise<boolean> {
public refreshNode(node: TreeNodeInfo): Promise<void> {
return this._objectExplorerService.refreshNode(node);
}
public signInNodeServer(node: TreeNodeInfo): void {
return this._objectExplorerService.signInNodeServer(node);
}
/** Getters */
public get currentNode(): TreeNodeInfo {
return this._objectExplorerService.currentNode;

Просмотреть файл

@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as vscode from 'vscode';
import SqlToolsServiceClient from '../languageservice/serviceclient';
import ConnectionManager from '../controllers/connectionManager';
import { CreateSessionCompleteNotification, SessionCreatedParameters, CreateSessionRequest } from '../models/contracts/objectExplorer/createSessionRequest';
@ -13,25 +14,40 @@ import { TreeItemCollapsibleState } from 'vscode';
import { RefreshRequest, RefreshParams } from '../models/contracts/objectExplorer/refreshSessionRequest';
import { CloseSessionRequest, CloseSessionParams } from '../models/contracts/objectExplorer/closeSessionRequest';
import { TreeNodeInfo } from './treeNodeInfo';
import { ConnectionProfile } from '../models/connectionProfile';
import { IConnectionCredentials } from '../models/interfaces';
import LocalizedConstants = require('../constants/localizedConstants');
import { AddConnectionTreeNode } from './addConnectionTreeNode';
import { AccountSignInTreeNode } from './accountSignInTreeNode';
import { ConnectTreeNode } from './connectTreeNode';
import { Deferred } from '../protocol';
import Constants = require('../constants/constants');
import { ObjectExplorerUtils } from './objectExplorerUtils';
import { ConnectionStore } from '../models/connectionStore';
export class ObjectExplorerService {
private _client: SqlToolsServiceClient;
private _currentNode: TreeNodeInfo;
private _treeNodeToChildrenMap: Map<TreeNodeInfo, TreeNodeInfo[]>;
private _treeNodeToChildrenMap: Map<TreeNodeInfo, vscode.TreeItem[]>;
private _nodePathToNodeLabelMap: Map<string, string>;
private _rootTreeNodeArray: Array<TreeNodeInfo>;
private _sessionIdToConnectionCredentialsMap: Map<string, ConnectionCredentials>;
// Deferred promise maps
private _sessionIdToPromiseMap: Map<string, Deferred<vscode.TreeItem>>;
private _expandParamsToPromiseMap: Map<ExpandParams, Deferred<TreeNodeInfo[]>>;
constructor(private _connectionManager: ConnectionManager,
private _objectExplorerProvider: ObjectExplorerProvider) {
this._connectionManager = _connectionManager;
this._client = this._connectionManager.client;
this._treeNodeToChildrenMap = new Map<TreeNodeInfo, TreeNodeInfo[]>();
this._treeNodeToChildrenMap = new Map<TreeNodeInfo, vscode.TreeItem[]>();
this._rootTreeNodeArray = new Array<TreeNodeInfo>();
this._sessionIdToConnectionCredentialsMap = new Map<string, ConnectionCredentials>();
this._nodePathToNodeLabelMap = new Map<string, string>();
this._sessionIdToPromiseMap = new Map<string, Deferred<vscode.TreeItem>>();
this._expandParamsToPromiseMap = new Map<ExpandParams, Deferred<TreeNodeInfo[]>>();
this._client.onNotification(CreateSessionCompleteNotification.type,
this.handleSessionCreatedNotification());
this._client.onNotification(ExpandCompleteNotification.type,
@ -43,10 +59,46 @@ export class ObjectExplorerService {
const handler = (result: SessionCreatedParameters) => {
if (result.success) {
let nodeLabel = this._nodePathToNodeLabelMap.get(result.rootNode.nodePath);
self._currentNode = TreeNodeInfo.fromNodeInfo(result.rootNode, result.sessionId, self.currentNode, nodeLabel);
self._rootTreeNodeArray.push(self.currentNode);
// if no node label, check if it has a name in saved profiles
// in case this call came from new query
let savedConnections = this._connectionManager.connectionStore.loadAllConnections();
for (let connection of savedConnections) {
if (connection.connectionCreds.server === result.rootNode.nodePath) {
nodeLabel = connection.label;
break;
}
}
// set connection and other things
if (self._currentNode) {
self._currentNode = TreeNodeInfo.fromNodeInfo(result.rootNode, result.sessionId,
self._currentNode, self._currentNode.connectionCredentials,
nodeLabel ? nodeLabel : result.rootNode.nodePath);
} else {
const credentials = this._sessionIdToConnectionCredentialsMap.get(result.sessionId);
self._currentNode = TreeNodeInfo.fromNodeInfo(result.rootNode, result.sessionId, self._currentNode,
credentials, nodeLabel ? nodeLabel : result.rootNode.nodePath);
}
self.updateNode(self._currentNode);
self._objectExplorerProvider.objectExplorerExists = true;
return self._objectExplorerProvider.refresh(undefined);
const promise = self._sessionIdToPromiseMap.get(result.sessionId);
// remove the sign in node once the session is created
if (self._treeNodeToChildrenMap.has(self._currentNode)) {
self._treeNodeToChildrenMap.delete(self._currentNode);
}
return promise.resolve(self._currentNode);
} else {
// failure
self.updateNode(self._currentNode);
self._currentNode = undefined;
let error = LocalizedConstants.connectErrorLabel;
if (result.errorMessage) {
error += ` : ${result.errorMessage}`;
}
self._connectionManager.vscodeWrapper.showErrorMessage(error);
const promise = self._sessionIdToPromiseMap.get(result.sessionId);
if (promise) {
return promise.resolve(undefined);
}
}
};
return handler;
@ -56,70 +108,155 @@ export class ObjectExplorerService {
const self = this;
const handler = (result: ExpandResponse) => {
if (result && result.nodes) {
const children = result.nodes.map(node => TreeNodeInfo.fromNodeInfo(node, self.currentNode.sessionId, self.currentNode));
self._currentNode.collapsibleState = TreeItemCollapsibleState.Expanded;
self._treeNodeToChildrenMap.set(self.currentNode, children);
return self._objectExplorerProvider.refresh(self.currentNode);
const children = result.nodes.map(node => TreeNodeInfo.fromNodeInfo(node, self._currentNode.sessionId,
self._currentNode, self._currentNode.connectionCredentials));
self._treeNodeToChildrenMap.set(self._currentNode, children);
const expandParams: ExpandParams = {
sessionId: result.sessionId,
nodePath: result.nodePath
}
for (let key of self._expandParamsToPromiseMap.keys()) {
if (key.sessionId === expandParams.sessionId &&
key.nodePath === expandParams.nodePath) {
let promise = self._expandParamsToPromiseMap.get(key);
return promise.resolve(children);
}
}
}
};
return handler;
}
private async expandNode(node: TreeNodeInfo, sessionId: string): Promise<boolean> {
private async expandNode(node: TreeNodeInfo, sessionId: string, promise: Deferred<TreeNodeInfo[]>): Promise<boolean> {
const expandParams: ExpandParams = {
sessionId: sessionId,
nodePath: node.nodePath
};
const response = await this._connectionManager.client.sendRequest(ExpandRequest.type, expandParams);
if (promise) {
this._expandParamsToPromiseMap.set(expandParams, promise);
}
return response;
}
async getChildren(element?: TreeNodeInfo): Promise<TreeNodeInfo[]> {
private updateNode(node: TreeNodeInfo): void {
for (let rootTreeNode of this._rootTreeNodeArray) {
if (rootTreeNode.connectionCredentials === node.connectionCredentials &&
rootTreeNode.label === node.label) {
const index = this._rootTreeNodeArray.indexOf(rootTreeNode);
this._rootTreeNodeArray[index] = node;
return;
}
}
this._rootTreeNodeArray.push(node);
}
async getChildren(element?: TreeNodeInfo): Promise<vscode.TreeItem[]> {
if (element) {
if (element !== this.currentNode) {
if (element !== this._currentNode) {
this._currentNode = element;
}
if (this._treeNodeToChildrenMap.get(this._currentNode)) {
// get cached children
if (this._treeNodeToChildrenMap.has(this._currentNode)) {
return this._treeNodeToChildrenMap.get(this._currentNode);
} else {
// expansion
await this.expandNode(element, element.sessionId);
return [];
// check if session exists
if (element.sessionId) {
// clean created session promise
this._sessionIdToPromiseMap.delete(element.sessionId);
// node expansion
let promise = new Deferred<TreeNodeInfo[]>();
this.expandNode(element, element.sessionId, promise);
return promise.then((children) => {
if (children) {
// clean expand session promise
for (const key of this._expandParamsToPromiseMap.keys()) {
if (key.sessionId === element.sessionId &&
key.nodePath === element.nodePath) {
this._expandParamsToPromiseMap.delete(key);
}
}
return children;
}
});
} else {
// start node session
let promise = new Deferred<TreeNodeInfo>();
this.createSession(promise, element.connectionCredentials);
return promise.then((node) => {
// If password wasn't given
if (!node) {
const signInNode = new AccountSignInTreeNode(element);
this._treeNodeToChildrenMap.set(element, [signInNode]);
return [signInNode];
}
// otherwise expand the node by refreshing the root
// to add connected context key
this._objectExplorerProvider.refresh(undefined);
});
}
}
} else {
// retrieve saved connections first when opening object explorer
// for the first time
if (!this._objectExplorerProvider.objectExplorerExists) {
let savedConnections = this._connectionManager.connectionStore.loadAllConnections();
savedConnections.forEach(async (conn) => {
let connectionCredentials = conn.connectionCreds;
let savedConnections = this._connectionManager.connectionStore.loadAllConnections();
if ((!this._objectExplorerProvider.objectExplorerExists ||
savedConnections.length !== this._rootTreeNodeArray.length) &&
savedConnections.length > 0) {
this._rootTreeNodeArray = [];
savedConnections.forEach((conn) => {
this._nodePathToNodeLabelMap.set(conn.connectionCreds.server, conn.label);
if (connectionCredentials) {
const connectionDetails = ConnectionCredentials.createConnectionDetails(connectionCredentials);
const response = await this._connectionManager.client.sendRequest(CreateSessionRequest.type, connectionDetails);
this._sessionIdToConnectionCredentialsMap.set(response.sessionId, connectionCredentials);
}
let node = new TreeNodeInfo(conn.label, Constants.disconnectedServerLabel,
TreeItemCollapsibleState.Collapsed,
undefined, undefined, Constants.serverLabel,
undefined, conn.connectionCreds, undefined);
this._rootTreeNodeArray.push(node);
});
return;
}
if (this._rootTreeNodeArray.length === 0) {
this.createSession();
return [];
} else {
this._objectExplorerProvider.objectExplorerExists = true;
return this._rootTreeNodeArray;
} else {
if (this._rootTreeNodeArray.length > 0) {
return this._rootTreeNodeArray;
} else {
return [new AddConnectionTreeNode()];
}
}
}
}
public async createSession(): Promise<string> {
const connectionUI = this._connectionManager.connectionUI;
const connectionCreds = await connectionUI.showConnections();
if (connectionCreds) {
this._nodePathToNodeLabelMap.set(connectionCreds.server, (<ConnectionProfile>connectionCreds).profileName);
const connectionDetails = ConnectionCredentials.createConnectionDetails(connectionCreds);
/**
* Create an OE session for the given connection credentials
* otherwise prompt the user to select a connection to make an
* OE out of
* @param connectionCredentials Connection Credentials for a node
*/
public async createSession(promise: Deferred<vscode.TreeItem>, connectionCredentials?: IConnectionCredentials): Promise<void> {
if (!connectionCredentials) {
const connectionUI = this._connectionManager.connectionUI;
connectionCredentials = await connectionUI.showConnections();
}
if (connectionCredentials) {
// show password prompt if SQL Login and password isn't saved
const shouldPromptForPassword = ConnectionCredentials.shouldPromptForPassword(connectionCredentials);
if (shouldPromptForPassword) {
// look up saved password
let password = await this._connectionManager.connectionStore.lookupPassword(connectionCredentials);
if (!password) {
let password = await this._connectionManager.connectionUI.promptForPassword();
if (!password) {
return promise.resolve(undefined);
}
}
connectionCredentials.password = password;
}
const connectionDetails = ConnectionCredentials.createConnectionDetails(connectionCredentials);
const response = await this._connectionManager.client.sendRequest(CreateSessionRequest.type, connectionDetails);
this._sessionIdToConnectionCredentialsMap.set(response.sessionId, connectionCreds);
return response.sessionId;
if (response) {
this._sessionIdToConnectionCredentialsMap.set(response.sessionId, connectionCredentials);
this._sessionIdToPromiseMap.set(response.sessionId, promise);
return;
}
}
}
@ -130,44 +267,71 @@ export class ObjectExplorerService {
return undefined;
}
public async removeObjectExplorerNode(node: TreeNodeInfo): Promise<void> {
public async removeObjectExplorerNode(node: TreeNodeInfo, isDisconnect: boolean = false): Promise<void> {
await this.closeSession(node);
const index = this._rootTreeNodeArray.indexOf(node, 0);
if (index > -1) {
this._rootTreeNodeArray.splice(index, 1);
if (!isDisconnect) {
const index = this._rootTreeNodeArray.indexOf(node, 0);
if (index > -1) {
this._rootTreeNodeArray.splice(index, 1);
}
}
const nodeUri = ObjectExplorerUtils.getNodeUri(node);
this._connectionManager.disconnect(nodeUri);
this._treeNodeToChildrenMap.delete(node);
this._nodePathToNodeLabelMap.delete(node.nodePath);
this._sessionIdToConnectionCredentialsMap.delete(node.sessionId);
if (this._sessionIdToPromiseMap.has(node.sessionId)) {
this._sessionIdToPromiseMap.delete(node.sessionId);
}
this._currentNode = undefined;
const nodeUri = node.nodePath + '_' + node.label;
this._connectionManager.disconnect(nodeUri);
await this._objectExplorerProvider.refresh(undefined);
if (isDisconnect) {
this._treeNodeToChildrenMap.set(node, [new ConnectTreeNode(node)]);
this.updateNode(node);
return this._objectExplorerProvider.refresh(undefined);
}
}
public async refreshNode(node: TreeNodeInfo): Promise<boolean> {
public async refreshNode(node: TreeNodeInfo): Promise<void> {
const refreshParams: RefreshParams = {
sessionId: node.sessionId,
nodePath: node.nodePath
};
const response = await this._connectionManager.client.sendRequest(RefreshRequest.type, refreshParams);
return response;
await this._connectionManager.client.sendRequest(RefreshRequest.type, refreshParams);
return this._objectExplorerProvider.refresh(node);
}
private async closeSession(node: TreeNodeInfo): Promise<void> {
const closeSessionParams: CloseSessionParams = {
sessionId: node.sessionId
};
const response = await this._connectionManager.client.sendRequest(CloseSessionRequest.type,
closeSessionParams);
if (response && response.success) {
this._sessionIdToConnectionCredentialsMap.delete(response.sessionId);
node.sessionId = undefined;
this._currentNode = node;
this._treeNodeToChildrenMap.set(this._currentNode, undefined);
this._currentNode.collapsibleState = TreeItemCollapsibleState.Collapsed;
public signInNodeServer(node: TreeNodeInfo): void {
if (this._treeNodeToChildrenMap.has(node)) {
this._treeNodeToChildrenMap.delete(node);
}
}
public async closeSession(node: TreeNodeInfo): Promise<void> {
if (node.sessionId) {
const closeSessionParams: CloseSessionParams = {
sessionId: node.sessionId
};
const response = await this._connectionManager.client.sendRequest(CloseSessionRequest.type,
closeSessionParams);
if (response && response.success) {
this._sessionIdToConnectionCredentialsMap.delete(response.sessionId);
node.nodeType = Constants.disconnectedServerLabel;
node.sessionId = undefined;
this.updateNode(node);
this._currentNode = node;
this._treeNodeToChildrenMap.set(this._currentNode, undefined);
return;
}
}
return;
}
/** Getters */
public get currentNode(): TreeNodeInfo {
return this._currentNode;
}
public get rootTreeNodeArray(): TreeNodeInfo[] {
return this._rootTreeNodeArray;
}
}

Просмотреть файл

@ -2,13 +2,33 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as path from 'path';
import { TreeNodeInfo } from './treeNodeInfo';
import { IConnectionProfile } from '../models/interfaces';
import Constants = require('../constants/constants');
export class ObjectExplorerUtils {
public static readonly rootPath: string = __dirname + '\\objectTypes\\';
public static readonly rootPath: string = path.join(__dirname, 'objectTypes');
public static iconPath(label: string): string {
if (label) {
return ObjectExplorerUtils.rootPath + `${label}.svg`;
return path.join(ObjectExplorerUtils.rootPath, `${label}.svg`);
}
}
public static getNodeUri(node: TreeNodeInfo): string {
while (node) {
if (node.nodeType === Constants.serverLabel) {
break;
}
node = node.parentNode;
}
const nodeUri = node.nodePath + '_' + node.label;
return nodeUri;
}
public static getNodeUriFromProfile(profile: IConnectionProfile): string {
const uri = profile.server + '_' + profile.profileName;
return uri;
}
}

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>

После

Ширина:  |  Высота:  |  Размер: 486 B

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>

После

Ширина:  |  Высота:  |  Размер: 486 B

Просмотреть файл

@ -6,6 +6,7 @@
import * as vscode from 'vscode';
import { NodeInfo } from '../models/contracts/objectExplorer/nodeInfo';
import { ObjectExplorerUtils } from './objectExplorerUtils';
import { IConnectionCredentials } from '../models/interfaces';
export class TreeNodeInfo extends vscode.TreeItem {
@ -17,6 +18,7 @@ export class TreeNodeInfo extends vscode.TreeItem {
private _errorMessage: string;
private _sessionId: string;
private _parentNode: TreeNodeInfo;
private _connectionCredentials: IConnectionCredentials;
constructor(
label: string,
@ -26,6 +28,7 @@ export class TreeNodeInfo extends vscode.TreeItem {
nodeStatus: string,
nodeType: string,
sessionId: string,
connectionCredentials: IConnectionCredentials,
parentNode: TreeNodeInfo
) {
super(label, collapsibleState);
@ -35,13 +38,19 @@ export class TreeNodeInfo extends vscode.TreeItem {
this._nodeType = nodeType;
this._sessionId = sessionId;
this._parentNode = parentNode;
this._connectionCredentials = connectionCredentials;
this.iconPath = ObjectExplorerUtils.iconPath(this.nodeType);
}
public static fromNodeInfo(nodeInfo: NodeInfo, sessionId: string, parentNode: TreeNodeInfo, label?: string): TreeNodeInfo {
public static fromNodeInfo(
nodeInfo: NodeInfo,
sessionId: string,
parentNode: TreeNodeInfo,
connectionCredentials: IConnectionCredentials,
label?: string): TreeNodeInfo {
const treeNodeInfo = new TreeNodeInfo(label ? label : nodeInfo.label, nodeInfo.nodeType,
vscode.TreeItemCollapsibleState.Collapsed, nodeInfo.nodePath, nodeInfo.nodeStatus,
nodeInfo.nodeType, sessionId, parentNode);
nodeInfo.nodeType, sessionId, connectionCredentials, parentNode);
return treeNodeInfo;
}
@ -78,6 +87,10 @@ export class TreeNodeInfo extends vscode.TreeItem {
return this._parentNode;
}
public get connectionCredentials(): IConnectionCredentials {
return this._connectionCredentials;
}
/** Setters */
public set nodePath(value: string) {
this._nodePath = value;
@ -110,4 +123,8 @@ export class TreeNodeInfo extends vscode.TreeItem {
public set parentNode(value: TreeNodeInfo) {
this._parentNode = value;
}
public set connectionCredentials(value: IConnectionCredentials) {
this._connectionCredentials = value;
}
}

Просмотреть файл

@ -27,7 +27,7 @@ export interface IMessageProtocol {
onMessage: Event<string>;
}
class Deferred<T> {
export class Deferred<T> {
promise: Promise<T>;
resolve: (value?: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;

Просмотреть файл

@ -0,0 +1,95 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import SqlToolsServiceClient from '../languageservice/serviceclient';
import ConnectionManager from '../controllers/connectionManager';
import { ScriptingRequest, ScriptingParams, ScriptOperation, ScriptingObject, ScriptOptions } from '../models/contracts/scripting/scriptingRequest';
import { TreeNodeInfo } from '../objectExplorer/treeNodeInfo';
import VscodeWrapper from '../controllers/vscodeWrapper';
export class ScriptingService {
private _client: SqlToolsServiceClient;
constructor(
private _connectionManager: ConnectionManager,
private _vscodeWrapper: VscodeWrapper
) {
this._client = this._connectionManager.client;
}
// map for the version of SQL Server (default is 140)
readonly scriptCompatibilityOptionMap = {
90: 'Script90Compat',
100: 'Script100Compat',
105: 'Script105Compat',
110: 'Script110Compat',
120: 'Script120Compat',
130: 'Script130Compat',
140: 'Script140Compat'
};
// map for the target database engine edition (default is Enterprise)
readonly targetDatabaseEngineEditionMap = {
0: 'SqlServerEnterpriseEdition',
1: 'SqlServerPersonalEdition',
2: 'SqlServerStandardEdition',
3: 'SqlServerEnterpriseEdition',
4: 'SqlServerExpressEdition',
5: 'SqlAzureDatabaseEdition',
6: 'SqlDatawarehouseEdition',
7: 'SqlServerStretchEdition'
};
/**
* Helper to get the object name and schema name
* @param node
*/
private getObjectNames(node: TreeNodeInfo): string[] {
let fullName = node.label;
let objects = fullName.split('.');
return objects;
}
public async scriptSelect(node: TreeNodeInfo, uri: string): Promise<string> {
const objectNames = this.getObjectNames(node);
let scriptingObject: ScriptingObject = {
type: node.nodeType,
schema: objectNames[objectNames.length-2],
name: objectNames[objectNames.length-1]
};
let serverInfo = this._connectionManager.getServerInfo(node.connectionCredentials);
let scriptOptions: ScriptOptions = {
scriptCreateDrop: 'ScriptSelect',
typeOfDataToScript: 'SchemaOnly',
scriptStatistics: 'ScriptStatsNone',
targetDatabaseEngineEdition:
serverInfo.engineEditionId ? this.targetDatabaseEngineEditionMap[serverInfo.engineEditionId] : 'SqlServerEnterpriseEdition',
targetDatabaseEngineType: serverInfo.isCloud ? 'SqlAzure': 'SingleInstance',
scriptCompatibilityOption: serverInfo.serverMajorVersion ?
this.scriptCompatibilityOptionMap[serverInfo.serverMajorVersion] : 'Script140Compat'
};
let scriptingParams : ScriptingParams = {
filePath: undefined,
scriptDestination: 'ToEditor',
connectionString: undefined,
scriptingObjects: [scriptingObject],
includeObjectCriteria: undefined,
excludeObjectCriteria: undefined,
includeSchemas: undefined,
excludeSchemas: undefined,
includeTypes: undefined,
excludeTypes: undefined,
scriptOptions: scriptOptions,
connectionDetails: undefined,
ownerURI: uri,
selectScript: undefined,
operation: ScriptOperation.Select
}
const result = await this._client.sendRequest(ScriptingRequest.type, scriptingParams);
return result.script;
}
}

Просмотреть файл

@ -17,6 +17,7 @@ import Interfaces = require('../models/interfaces');
import { Timer } from '../models/utils';
import * as Utils from '../models/utils';
import VscodeWrapper from '../controllers/vscodeWrapper';
import { ObjectExplorerUtils} from '../objectExplorer/objectExplorerUtils';
/**
* The different tasks for managing connection profiles.
@ -182,6 +183,26 @@ export class ConnectionUI {
});
}
/**
* Prompt the user for password
*/
public promptForPassword(): Promise<string> {
const self = this;
return new Promise<string>((resolve, reject) => {
let question: IQuestion = {
type: QuestionTypes.password,
name: LocalizedConstants.passwordPrompt,
message: LocalizedConstants.passwordPrompt,
placeHolder: LocalizedConstants.passwordPlaceholder
};
self._prompter.promptSingle(question).then((result: string) => {
resolve(result);
}).catch(err => {
reject(err);
});
});
}
/**
* Prompt the user to change language mode to SQL.
* @returns resolves to true if the user changed the language mode to SQL.
@ -434,7 +455,7 @@ export class ConnectionUI {
const self = this;
let uri = self.vscodeWrapper.activeTextEditorUri;
if (!uri) {
uri = profile.server + '_' + profile.profileName;
uri = ObjectExplorerUtils.getNodeUriFromProfile(profile);
}
return self.connectionManager.connect(uri, profile).then(result => {
if (result) {

Просмотреть файл

@ -205,18 +205,18 @@ suite('MainController Tests', () => {
test('onNewQuery should call the new query and new connection' , () => {
untitledSqlDocumentService.setup(x => x.newQuery()).returns(() => Promise.resolve(TypeMoq.It.isAny()));
connectionManager.setup(x => x.onNewConnection()).returns(() => Promise.resolve(true));
connectionManager.setup(x => x.onNewConnection()).returns(() => Promise.resolve(TypeMoq.It.isAny()));
return mainController.onNewQuery(undefined).then(result => {
untitledSqlDocumentService.verify(x => x.newQuery(), TypeMoq.Times.once());
connectionManager.verify(x => x.onNewConnection(), TypeMoq.Times.once());
connectionManager.verify(x => x.onNewConnection(), TypeMoq.Times.atLeastOnce());
});
});
test('onNewQuery should not call the new connection if new query fails' , done => {
untitledSqlDocumentService.setup(x => x.newQuery()).returns(() => { return Promise.reject<vscode.Uri>('error'); } );
connectionManager.setup(x => x.onNewConnection()).returns(() => { return Promise.resolve(true); } );
connectionManager.setup(x => x.onNewConnection()).returns(() => { return Promise.resolve(TypeMoq.It.isAny()); } );
mainController.onNewQuery(undefined).catch(error => {
untitledSqlDocumentService.verify(x => x.newQuery(), TypeMoq.Times.once());

Просмотреть файл

@ -35,12 +35,12 @@ suite('Object Explorer Tests', () => {
test('Test Create Session', () => {
expect(objectExplorerService.object.currentNode, 'Current Node should be undefined').is.equal(undefined);
expect(objectExplorerProvider.objectExplorerExists, 'Object Explorer should not exist until started').is.equal(undefined);
objectExplorerService.setup(s => s.createSession()).returns(() => {
objectExplorerService.setup(s => s.createSession(TypeMoq.It.isAny())).returns(() => {
objectExplorerService.setup(s => s.currentNode).returns(() => TypeMoq.It.isAny());
objectExplorerProvider.objectExplorerExists = true;
return Promise.resolve(TypeMoq.It.isAnyString());
return Promise.resolve(TypeMoq.It.isAny());
});
objectExplorerProvider.createSession().then(sessionId => {
objectExplorerProvider.createSession(TypeMoq.It.isAny()).then(sessionId => {
expect(sessionId, 'Session Id should not be undefined').is.not.equal(undefined);
expect(objectExplorerService.object.currentNode, 'Current Node should not be undefined').is.not.equal(undefined);
expect(objectExplorerProvider.objectExplorerExists, 'Object Explorer session should exist').is.equal(true);
@ -62,15 +62,14 @@ suite('Object Explorer Tests', () => {
expect(credentials, 'Connection Credentials should not be null').is.not.equal(undefined);
});
test('Test remove Object Explorer node', () => {
test('Test remove Object Explorer node', async () => {
let isNodeDeleted = false;
objectExplorerService.setup(s => s.removeObjectExplorerNode(TypeMoq.It.isAny())).returns(() => {
objectExplorerService.setup(s => s.removeObjectExplorerNode(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
isNodeDeleted = true;
return Promise.resolve(undefined);
});
objectExplorerProvider.removeObjectExplorerNode(TypeMoq.It.isAny()).then(() => {
expect(isNodeDeleted, 'Node should be deleted').is.equal(true);
});
objectExplorerProvider.removeObjectExplorerNode(TypeMoq.It.isAny(), TypeMoq.It.isAny());
expect(isNodeDeleted, 'Node should be deleted').is.equal(true);
});
test('Test Get Children from Object Explorer Provider', () => {

Просмотреть файл

@ -490,7 +490,7 @@ suite('Per File Connection Tests', () => {
let connectionManagerMock: TypeMoq.IMock<ConnectionManager> = TypeMoq.Mock.ofType(ConnectionManager);
connectionManagerMock.setup(x => x.isConnected(TypeMoq.It.isAny())).returns(() => false);
connectionManagerMock.setup(x => x.isConnected(TypeMoq.It.isAny())).returns(() => true);
connectionManagerMock.setup(x => x.onNewConnection()).returns(() => Promise.resolve(false));
connectionManagerMock.setup(x => x.onNewConnection()).returns(() => Promise.resolve(undefined));
let controller: MainController = new MainController(contextMock.object,
connectionManagerMock.object,