Revert "Revert "Add a command line interface for connecting to a SQL Server (#3047)""

This reverts commit a747b6a500.
This commit is contained in:
Karl Burtram
2018-10-31 20:32:17 -07:00
parent a747b6a500
commit cffc18d5ea
7 changed files with 318 additions and 22 deletions

View File

@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export interface ICommandLineProcessing {
_serviceBrand: any;
/**
* Interprets the various Azure Data Studio-specific command line switches and
* performs the requisite tasks such as connecting to a server
*/
processCommandLine() : void;
}
export const ICommandLineProcessing = createDecorator<ICommandLineProcessing>('commandLineService');

View File

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { ICommandLineProcessing } from 'sql/parts/commandLine/common/commandLine';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import * as Constants from 'sql/parts/connection/common/constants';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import * as platform from 'vs/platform/registry/common/platform';
import { ConnectionProviderProperties, IConnectionProviderRegistry, Extensions as ConnectionProviderExtensions } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export class CommandLineService implements ICommandLineProcessing {
private _connectionProfile : ConnectionProfile;
private _showConnectionDialog: boolean;
constructor(
@IConnectionManagementService private _connectionManagementService : IConnectionManagementService,
@ICapabilitiesService private _capabilitiesService : ICapabilitiesService,
@IEnvironmentService private _environmentService : IEnvironmentService,
@IQueryEditorService private _queryEditorService : IQueryEditorService,
@IObjectExplorerService private _objectExplorerService : IObjectExplorerService,
@IEditorService private _editorService : IEditorService,
)
{
let profile = null;
if (this._environmentService && this._environmentService.args.server) {
profile = new ConnectionProfile(_capabilitiesService, null);
// We want connection store to use any matching password it finds
profile.savePassword = true;
profile.providerName = Constants.mssqlProviderName;
profile.serverName = _environmentService.args.server;
profile.databaseName = _environmentService.args.database ? _environmentService.args.database : '';
profile.userName = _environmentService.args.user ? _environmentService.args.user : '';
profile.authenticationType = _environmentService.args.integrated ? 'Integrated' : 'SqlLogin';
profile.connectionName = '';
profile.setOptionValue('applicationName', Constants.applicationName);
profile.setOptionValue('databaseDisplayName', profile.databaseName);
profile.setOptionValue('groupId', profile.groupId);
}
this._connectionProfile = profile;
const registry = platform.Registry.as<IConnectionProviderRegistry>(ConnectionProviderExtensions.ConnectionProviderContributions);
let sqlProvider = registry.getProperties( Constants.mssqlProviderName);
// We can't connect to object explorer until the MSSQL connection provider is registered
if (sqlProvider) {
this.processCommandLine();
} else {
registry.onNewProvider(e => {
if (e.id === Constants.mssqlProviderName)
{
this.processCommandLine();
}
});
}
}
public _serviceBrand: any;
public processCommandLine(): void {
if (!this._connectionProfile && !this._connectionManagementService.hasRegisteredServers()) {
// prompt the user for a new connection on startup if no profiles are registered
this._connectionManagementService.showConnectionDialog();
} else if (this._connectionProfile) {
this._connectionManagementService.connectIfNotConnected(this._connectionProfile, 'connection')
.then(result => TaskUtilities.newQuery(this._connectionProfile,
this._connectionManagementService,
this._queryEditorService,
this._objectExplorerService,
this._editorService))
.catch(() => {});
}
}
}

View File

@@ -77,7 +77,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
private _onConnectRequestSent = new Emitter<void>();
private _onConnectionChanged = new Emitter<IConnectionParams>();
private _onLanguageFlavorChanged = new Emitter<sqlops.DidChangeLanguageFlavorParams>();
private _connectionGlobalStatus = new ConnectionGlobalStatus(this._statusBarService);
private _configurationEditService: ConfigurationEditingService;
@@ -124,16 +123,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
100 /* High Priority */
));
if (_capabilitiesService && Object.keys(_capabilitiesService.providers).length > 0 && !this.hasRegisteredServers()) {
// prompt the user for a new connection on startup if no profiles are registered
this.showConnectionDialog();
} else if (_capabilitiesService && !this.hasRegisteredServers()) {
_capabilitiesService.onCapabilitiesRegistered(e => {
// prompt the user for a new connection on startup if no profiles are registered
this.showConnectionDialog();
});
}
const registry = platform.Registry.as<IConnectionProviderRegistry>(ConnectionProviderExtensions.ConnectionProviderContributions);
let providerRegistration = (p: { id: string, properties: ConnectionProviderProperties }) => {
@@ -282,29 +271,30 @@ export class ConnectionManagementService extends Disposable implements IConnecti
* @param options to use after the connection is complete
*/
private tryConnect(connection: IConnectionProfile, owner: IConnectableInput, options?: IConnectionCompletionOptions): Promise<IConnectionResult> {
let self = this;
return new Promise<IConnectionResult>((resolve, reject) => {
// Load the password if it's not already loaded
this._connectionStore.addSavedPassword(connection).then(result => {
self._connectionStore.addSavedPassword(connection).then(result => {
let newConnection = result.profile;
let foundPassword = result.savedCred;
// If there is no password, try to load it from an existing connection
if (!foundPassword && this._connectionStore.isPasswordRequired(newConnection)) {
let existingConnection = this._connectionStatusManager.findConnectionProfile(connection);
if (!foundPassword && self._connectionStore.isPasswordRequired(newConnection)) {
let existingConnection = self._connectionStatusManager.findConnectionProfile(connection);
if (existingConnection && existingConnection.connectionProfile) {
newConnection.password = existingConnection.connectionProfile.password;
foundPassword = true;
}
}
// If the password is required and still not loaded show the dialog
if (!foundPassword && this._connectionStore.isPasswordRequired(newConnection) && !newConnection.password) {
resolve(this.showConnectionDialogOnError(connection, owner, { connected: false, errorMessage: undefined, callStack: undefined, errorCode: undefined }, options));
if (!foundPassword && self._connectionStore.isPasswordRequired(newConnection) && !newConnection.password) {
resolve(self.showConnectionDialogOnError(connection, owner, { connected: false, errorMessage: undefined, callStack: undefined, errorCode: undefined }, options));
} else {
// Try to connect
this.connectWithOptions(newConnection, owner.uri, options, owner).then(connectionResult => {
self.connectWithOptions(newConnection, owner.uri, options, owner).then(connectionResult => {
if (!connectionResult.connected && !connectionResult.errorHandled) {
// If connection fails show the dialog
resolve(this.showConnectionDialogOnError(connection, owner, connectionResult, options));
resolve(self.showConnectionDialogOnError(connection, owner, connectionResult, options));
} else {
//Resolve with the connection result
resolve(connectionResult);
@@ -390,7 +380,15 @@ export class ConnectionManagementService extends Disposable implements IConnecti
if (this._connectionStatusManager.isConnected(ownerUri)) {
resolve(this._connectionStatusManager.getOriginalOwnerUri(ownerUri));
} else {
this.connect(connection, ownerUri).then(connectionResult => {
const options: IConnectionCompletionOptions = {
// Should saving the connection be a command line switch?
saveTheConnection : true,
showConnectionDialogOnError : true,
showDashboard : purpose === 'dashboard',
params : undefined,
showFirewallRuleOnError : true,
};
this.connect(connection, ownerUri, options).then(connectionResult => {
if (connectionResult && connectionResult.connected) {
resolve(this._connectionStatusManager.getOriginalOwnerUri(ownerUri));
} else {

View File

@@ -0,0 +1,176 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as Constants from 'sql/parts/connection/common/constants';
import * as Utils from 'sql/parts/connection/common/utils';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import * as sqlops from 'sqlops';
import { TPromise } from 'vs/base/common/winjs.base';
import * as assert from 'assert';
import * as TypeMoq from 'typemoq';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { CommandLineService } from 'sql/parts/commandLine/common/commandLineService';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { CapabilitiesService, ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { CapabilitiesTestService } from 'sqltest/stubs/capabilitiesTestService';
import { QueryEditorService } from 'sql/parts/query/services/queryEditorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import {
IConnectionManagementService, IConnectionDialogService, INewConnectionParams,
ConnectionType, IConnectableInput, IConnectionCompletionOptions, IConnectionCallbacks,
IConnectionParams, IConnectionResult, IServerGroupController, IServerGroupDialogCallbacks,
RunQueryOnConnectionMode
} from 'sql/parts/connection/common/connectionManagement';
import { ConnectionStore } from 'sql/parts/connection/common/connectionStore';
import { TestConnectionManagementService } from 'sqltest/stubs/connectionManagementService.test';
class TestParsedArgs implements ParsedArgs{
[arg: string]: any;
_: string[];
aad?: boolean;
add?: boolean;
database?:string;
debugBrkPluginHost?: string;
debugBrkSearch?: string;
debugId?: string;
debugPluginHost?: string;
debugSearch?: string;
diff?: boolean;
'disable-crash-reporter'?: string;
'disable-extension'?: string | string[];
'disable-extensions'?: boolean;
'disable-restore-windows'?: boolean;
'disable-telemetry'?: boolean;
'disable-updates'?: string;
'driver'?: string;
'enable-proposed-api'?: string | string[];
'export-default-configuration'?: string;
'extensions-dir'?: string;
extensionDevelopmentPath?: string;
extensionTestsPath?: string;
'file-chmod'?: boolean;
'file-write'?: boolean;
'folder-uri'?: string | string[];
goto?: boolean;
help?: boolean;
'install-extension'?: string | string[];
'install-source'?: string;
integrated?: boolean;
'list-extensions'?: boolean;
locale?: string;
log?: string;
logExtensionHostCommunication?: boolean;
'max-memory'?: number;
'new-window'?: boolean;
'open-url'?: boolean;
performance?: boolean;
'prof-append-timers'?: string;
'prof-startup'?: string;
'prof-startup-prefix'?: string;
'reuse-window'?: boolean;
server?: string;
'show-versions'?: boolean;
'skip-add-to-recently-opened'?: boolean;
'skip-getting-started'?: boolean;
'skip-release-notes'?: boolean;
status?: boolean;
'sticky-quickopen'?: boolean;
'uninstall-extension'?: string | string[];
'unity-launch'?: boolean; // Always open a new window, except if opening the first window or opening a file or folder as part of the launch.
'upload-logs'?: string;
user?: string;
'user-data-dir'?: string;
_urls?: string[];
verbose?: boolean;
version?: boolean;
wait?: boolean;
waitMarkerFilePath?: string;
}
suite('commandLineService tests', () => {
let capabilitiesService: CapabilitiesTestService;
let commandLineService : CommandLineService;
let environmentService : TypeMoq.Mock<EnvironmentService>;
let queryEditorService : TypeMoq.Mock<QueryEditorService>;
let editorService:TypeMoq.Mock<IEditorService>;
let objectExplorerService : TypeMoq.Mock<ObjectExplorerService>;
let connectionStore: TypeMoq.Mock<ConnectionStore>;
setup(() => {
capabilitiesService = new CapabilitiesTestService();
connectionStore = TypeMoq.Mock.ofType(ConnectionStore);
});
function getCommandLineService(connectionManagementService : IConnectionManagementService,
environmentService? : IEnvironmentService,
capabilitiesService? : ICapabilitiesService
) : CommandLineService
{
let service= new CommandLineService(
connectionManagementService,
capabilitiesService,
environmentService,
undefined,
undefined,
undefined
);
return service;
}
test('processCommandLine shows connection dialog by default', done => {
const connectionManagementService : TypeMoq.Mock<IConnectionManagementService>
= TypeMoq.Mock.ofType<IConnectionManagementService>(TestConnectionManagementService, TypeMoq.MockBehavior.Strict);
connectionManagementService.setup((c) => c.showConnectionDialog()).verifiable();
connectionManagementService.setup(c => c.hasRegisteredServers()).returns(() => false);
connectionManagementService.setup(c => c.connectIfNotConnected(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.verifiable(TypeMoq.Times.never());
let service = getCommandLineService(connectionManagementService.object);
service.processCommandLine();
connectionManagementService.verifyAll();
done();
});
test('processCommandLine does nothing if registered servers exist and no server name is provided', done => {
const connectionManagementService : TypeMoq.Mock<IConnectionManagementService>
= TypeMoq.Mock.ofType<IConnectionManagementService>(TestConnectionManagementService, TypeMoq.MockBehavior.Strict);
connectionManagementService.setup((c) => c.showConnectionDialog()).verifiable(TypeMoq.Times.never());
connectionManagementService.setup(c => c.hasRegisteredServers()).returns(() => true);
connectionManagementService.setup(c => c.connectIfNotConnected(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.verifiable(TypeMoq.Times.never());
let service = getCommandLineService(connectionManagementService.object);
service.processCommandLine();
connectionManagementService.verifyAll();
done();
});
test('processCommandLine opens a new connection if a server name is passed', done => {
const connectionManagementService : TypeMoq.Mock<IConnectionManagementService>
= TypeMoq.Mock.ofType<IConnectionManagementService>(TestConnectionManagementService, TypeMoq.MockBehavior.Strict);
const environmentService : TypeMoq.Mock<IEnvironmentService> = TypeMoq.Mock.ofType<IEnvironmentService>(EnvironmentService);
const args : TestParsedArgs = new TestParsedArgs();
args.server = 'myserver';
args.database = 'mydatabase';
environmentService.setup(e => e.args).returns(() => args).verifiable(TypeMoq.Times.atLeastOnce());
connectionManagementService.setup((c) => c.showConnectionDialog()).verifiable(TypeMoq.Times.never());
connectionManagementService.setup(c => c.hasRegisteredServers()).returns(() => true).verifiable(TypeMoq.Times.atMostOnce());
connectionManagementService.setup(c => c.connectIfNotConnected(TypeMoq.It.isAny(), 'connection'))
.returns(() => new Promise<string>((resolve, reject) => { reject('unused');}))
.verifiable(TypeMoq.Times.once());
let service = getCommandLineService(connectionManagementService.object, environmentService.object, capabilitiesService);
service.processCommandLine();
environmentService.verifyAll();
connectionManagementService.verifyAll();
done();
});
});

View File

@@ -62,6 +62,13 @@ export interface ParsedArgs {
'upload-logs'?: string;
'driver'?: string;
'driver-verbose'?: boolean;
// {{SQL CARBON EDIT}}
aad?: boolean;
database?: string;
integrated?: boolean;
server?: string;
user?: string;
// {{SQL CARBON EDIT}}
}
export const IEnvironmentService = createDecorator<IEnvironmentService>('environmentService');

View File

@@ -33,7 +33,12 @@ const options: minimist.Opts = {
'export-default-configuration',
'install-source',
'upload-logs',
'driver'
'driver',
// {{SQL CARBON EDIT}}
'database',
'server',
'user',
// {{SQL CARBON EDIT}}
],
boolean: [
'help',
@@ -66,7 +71,11 @@ const options: minimist.Opts = {
'status',
'file-write',
'file-chmod',
'driver-verbose'
'driver-verbose',
// {{SQL CARBON EDIT}}
'aad',
'integrated',
// {{SQL CARBON EDIT}}
],
alias: {
add: 'a',
@@ -85,6 +94,12 @@ const options: minimist.Opts = {
'debugBrkPluginHost': 'inspect-brk-extensions',
'debugSearch': 'inspect-search',
'debugBrkSearch': 'inspect-brk-search',
// {{SQL CARBON EDIT}}
database: 'D',
integrated: 'E',
server: 'S',
user: 'U',
// {{SQL CARBON EDIT}}
}
};

View File

@@ -172,6 +172,10 @@ import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'
import { WorkbenchThemeService } from 'vs/workbench/services/themes/electron-browser/workbenchThemeService';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IUriDisplayService, UriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
// {{SQL CARBON EDIT}}
import { ICommandLineProcessing } from 'sql/parts/commandLine/common/commandLine';
import { CommandLineService } from 'sql/parts/commandLine/common/commandLineService';
// {{SQL CARBON EDIT}}
interface WorkbenchParams {
configuration: IWindowConfiguration;
@@ -575,7 +579,9 @@ export class Workbench extends Disposable implements IPartService {
serviceCollection.set(IAccountManagementService, accountManagementService);
serviceCollection.set(IAccountPickerService, this.instantiationService.createInstance(AccountPickerService));
serviceCollection.set(IProfilerService, this.instantiationService.createInstance(ProfilerService));
// {{SQL CARBON EDIT}}
serviceCollection.set(ICommandLineProcessing, this.instantiationService.createInstance(CommandLineService));
// {{SQL CARBON EDIT}}
this._register(toDisposable(() => connectionManagementService.shutdown()));
this._register(toDisposable(() => accountManagementService.shutdown()));
this._register(toDisposable(() => capabilitiesService.shutdown()));