diff --git a/extensions/machine-learning/package.json b/extensions/machine-learning/package.json index bc28676b64..c3f537c05d 100644 --- a/extensions/machine-learning/package.json +++ b/extensions/machine-learning/package.json @@ -7,7 +7,7 @@ "preview": true, "engines": { "vscode": "^1.25.0", - "azdata": ">=1.13.0" + "azdata": ">=1.18.0" }, "activationEvents": [ "onCommand:ml.command.managePackages", diff --git a/extensions/machine-learning/src/common/queryRunner.ts b/extensions/machine-learning/src/common/queryRunner.ts index 18b3922c23..592ca98c4b 100644 --- a/extensions/machine-learning/src/common/queryRunner.ts +++ b/extensions/machine-learning/src/common/queryRunner.ts @@ -19,8 +19,8 @@ EXEC sp_execute_external_script @script=N'import pkg_resources import pandas OutputDataSet = pandas.DataFrame([(d.project_name, d.version) for d in pkg_resources.working_set])' -select e.name, version from sys.external_libraries e join @tablevar t on e.name = t.name -where [language] = 'PYTHON' +select t.name, (CASE WHEN e.name is NULL THEN 1 ELSE 0 END) as isReadOnly , version from @tablevar t +left join sys.external_libraries e on e.name = t.name and upper(e.[language]) = 'PYTHON' `; const listRPackagesQuery = ` @@ -30,9 +30,8 @@ EXEC sp_execute_external_script @language=N'R', @script=N' OutputDataSet <- as.data.frame(installed.packages()[,c(1,3)])' - -select e.name, version from sys.external_libraries e join @tablevar t on e.name = t.name -where [language] = 'R' +select t.name, (CASE WHEN e.name is NULL THEN 1 ELSE 0 END) as isReadOnly , version from @tablevar t +left join sys.external_libraries e on e.name = t.name and upper(e.[language]) = 'R' `; const checkMlInstalledQuery = ` @@ -100,7 +99,8 @@ export class QueryRunner { packages = result.rows.map(row => { return { name: row[0].displayValue, - version: row[1].displayValue + readonly: row[1].displayValue === '1', + version: row[2].displayValue }; }); } diff --git a/extensions/machine-learning/src/packageManagement/packageManageProviderBase.ts b/extensions/machine-learning/src/packageManagement/packageManageProviderBase.ts index 36549d9523..633244d1fa 100644 --- a/extensions/machine-learning/src/packageManagement/packageManageProviderBase.ts +++ b/extensions/machine-learning/src/packageManagement/packageManageProviderBase.ts @@ -70,8 +70,17 @@ export abstract class SqlPackageManageProviderBase { * @param packages Packages to uninstall */ public async uninstallPackages(packages: nbExtensionApis.IPackageDetails[], databaseName: string): Promise { + let allPackages = await this.listPackages(databaseName); + if (packages) { - await Promise.all(packages.map(x => this.executeScripts(ScriptMode.Uninstall, x, databaseName))); + await Promise.all(packages.map(x => { + const originalPackage = allPackages.find(p => p.name === x.name && p.version === x.version); + if (originalPackage && originalPackage.readonly) { + return Promise.reject(`Cannot uninstalled system package '${x.name}'`); + } else { + return this.executeScripts(ScriptMode.Uninstall, x, databaseName); + } + })); } } diff --git a/extensions/machine-learning/src/packageManagement/sqlRPackageManageProvider.ts b/extensions/machine-learning/src/packageManagement/sqlRPackageManageProvider.ts index 841067224b..6e2f163632 100644 --- a/extensions/machine-learning/src/packageManagement/sqlRPackageManageProvider.ts +++ b/extensions/machine-learning/src/packageManagement/sqlRPackageManageProvider.ts @@ -68,9 +68,10 @@ export class SqlRPackageManageProvider extends SqlPackageManageProviderBase impl let credentials = await this._apiWrapper.getCredentials(connection.connectionId); if (connection) { + let server = connection.serverName.replace('\\', '\\\\'); let database = databaseName ? `, database="${databaseName}"` : ''; const auth = connection.userName ? `, uid="${connection.userName}", pwd="${credentials[azdata.ConnectionOptionSpecialType.password]}"` : ''; - let connectionParts = `server="${connection.serverName}"${auth}${database}`; + let connectionParts = `server="${server}"${auth}${database}`; let rCommandScript = scriptMode === ScriptMode.Install ? 'sql_install.packages' : 'sql_remove.packages'; let scripts: string[] = [ diff --git a/extensions/machine-learning/src/test/queryRunner.test.ts b/extensions/machine-learning/src/test/queryRunner.test.ts index 7786da774f..d2c3f9e61b 100644 --- a/extensions/machine-learning/src/test/queryRunner.test.ts +++ b/extensions/machine-learning/src/test/queryRunner.test.ts @@ -21,32 +21,32 @@ function createContext(): TestContext { apiWrapper: TypeMoq.Mock.ofType(ApiWrapper), queryProvider: { providerId: '', - cancelQuery: () => {return Promise.reject();}, - runQuery: () => {return Promise.reject();}, - runQueryStatement: () => {return Promise.reject();}, - runQueryString: () => {return Promise.reject();}, + cancelQuery: () => { return Promise.reject(); }, + runQuery: () => { return Promise.reject(); }, + runQueryStatement: () => { return Promise.reject(); }, + runQueryString: () => { return Promise.reject(); }, runQueryAndReturn: () => { return Promise.reject(); }, - parseSyntax: () => {return Promise.reject();}, - getQueryRows: () => {return Promise.reject();}, - disposeQuery: () => {return Promise.reject();}, - saveResults: () => {return Promise.reject();}, - setQueryExecutionOptions: () => {return Promise.reject();}, - registerOnQueryComplete: () => {return Promise.reject();}, - registerOnBatchStart: () => {return Promise.reject();}, - registerOnBatchComplete: () => {return Promise.reject();}, - registerOnResultSetAvailable: () => {return Promise.reject();}, - registerOnResultSetUpdated: () => {return Promise.reject();}, - registerOnMessage: () => {return Promise.reject();}, - commitEdit: () => {return Promise.reject();}, - createRow: () => {return Promise.reject();}, - deleteRow: () => {return Promise.reject();}, - disposeEdit: () => {return Promise.reject();}, - initializeEdit: () => {return Promise.reject();}, - revertCell: () => {return Promise.reject();}, - revertRow: () => {return Promise.reject();}, - updateCell: () => {return Promise.reject();}, - getEditRows: () => {return Promise.reject();}, - registerOnEditSessionReady: () => {return Promise.reject();}, + parseSyntax: () => { return Promise.reject(); }, + getQueryRows: () => { return Promise.reject(); }, + disposeQuery: () => { return Promise.reject(); }, + saveResults: () => { return Promise.reject(); }, + setQueryExecutionOptions: () => { return Promise.reject(); }, + registerOnQueryComplete: () => { return Promise.reject(); }, + registerOnBatchStart: () => { return Promise.reject(); }, + registerOnBatchComplete: () => { return Promise.reject(); }, + registerOnResultSetAvailable: () => { return Promise.reject(); }, + registerOnResultSetUpdated: () => { return Promise.reject(); }, + registerOnMessage: () => { return Promise.reject(); }, + commitEdit: () => { return Promise.reject(); }, + createRow: () => { return Promise.reject(); }, + deleteRow: () => { return Promise.reject(); }, + disposeEdit: () => { return Promise.reject(); }, + initializeEdit: () => { return Promise.reject(); }, + revertCell: () => { return Promise.reject(); }, + revertRow: () => { return Promise.reject(); }, + updateCell: () => { return Promise.reject(); }, + getEditRows: () => { return Promise.reject(); }, + registerOnEditSessionReady: () => { return Promise.reject(); }, } }; } @@ -54,7 +54,7 @@ function createContext(): TestContext { describe('Query Runner', () => { it('getPythonPackages Should return empty list if not provider found', async function (): Promise { let testContext = createContext(); - let connection = new azdata.connection.ConnectionProfile(); + let connection = new azdata.connection.ConnectionProfile(); let queryRunner = new QueryRunner(testContext.apiWrapper.object); let queryProvider: azdata.QueryProvider; testContext.apiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => queryProvider); @@ -65,7 +65,7 @@ describe('Query Runner', () => { it('getPythonPackages Should return empty list if not provider throws', async function (): Promise { let testContext = createContext(); - let connection = new azdata.connection.ConnectionProfile(); + let connection = new azdata.connection.ConnectionProfile(); let queryRunner = new QueryRunner(testContext.apiWrapper.object); testContext.queryProvider.runQueryAndReturn = () => { return Promise.reject(); }; testContext.apiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => testContext.queryProvider); @@ -82,6 +82,11 @@ describe('Query Runner', () => { isNull: false, invariantCultureDisplayValue: '' }, { + displayValue: '0', + isNull: false, + invariantCultureDisplayValue: '' + }, + { displayValue: '1.1.1', isNull: false, invariantCultureDisplayValue: '' @@ -90,6 +95,10 @@ describe('Query Runner', () => { displayValue: 'p2', isNull: false, invariantCultureDisplayValue: '' + }, { + displayValue: '1', + isNull: false, + invariantCultureDisplayValue: '' }, { displayValue: '1.1.2', isNull: false, @@ -99,20 +108,22 @@ describe('Query Runner', () => { let expected = [ { 'name': 'p1', + 'readonly': false, 'version': '1.1.1' }, { 'name': 'p2', + 'readonly': true, 'version': '1.1.2' } ]; - let result : azdata.SimpleExecuteResult = { + let result: azdata.SimpleExecuteResult = { rowCount: 2, columnInfo: [], rows: rows, }; - let connection = new azdata.connection.ConnectionProfile(); + let connection = new azdata.connection.ConnectionProfile(); let queryRunner = new QueryRunner(testContext.apiWrapper.object); testContext.queryProvider.runQueryAndReturn = () => { return Promise.resolve(result); }; testContext.apiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => testContext.queryProvider); @@ -128,12 +139,12 @@ describe('Query Runner', () => { ]; let expected: IPackageDetails[] = []; - let result : azdata.SimpleExecuteResult = { + let result: azdata.SimpleExecuteResult = { rowCount: 2, columnInfo: [], rows: rows, }; - let connection = new azdata.connection.ConnectionProfile(); + let connection = new azdata.connection.ConnectionProfile(); let queryRunner = new QueryRunner(testContext.apiWrapper.object); testContext.queryProvider.runQueryAndReturn = () => { return Promise.resolve(result); }; testContext.apiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => testContext.queryProvider); @@ -148,12 +159,12 @@ describe('Query Runner', () => { let rows: azdata.DbCellValue[][] = [ ]; - let result : azdata.SimpleExecuteResult = { + let result: azdata.SimpleExecuteResult = { rowCount: 2, columnInfo: [], rows: rows, }; - let connection = new azdata.connection.ConnectionProfile(); + let connection = new azdata.connection.ConnectionProfile(); let queryRunner = new QueryRunner(testContext.apiWrapper.object); testContext.queryProvider.runQueryAndReturn = () => { return Promise.resolve(result); }; testContext.apiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => testContext.queryProvider); @@ -173,12 +184,12 @@ describe('Query Runner', () => { ]; let expected = true; - let result : azdata.SimpleExecuteResult = { + let result: azdata.SimpleExecuteResult = { rowCount: 2, columnInfo: [], rows: rows, }; - let connection = new azdata.connection.ConnectionProfile(); + let connection = new azdata.connection.ConnectionProfile(); let queryRunner = new QueryRunner(testContext.apiWrapper.object); testContext.queryProvider.runQueryAndReturn = () => { return Promise.resolve(result); }; testContext.apiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => testContext.queryProvider); @@ -198,12 +209,12 @@ describe('Query Runner', () => { ]; let expected = false; - let result : azdata.SimpleExecuteResult = { + let result: azdata.SimpleExecuteResult = { rowCount: 2, columnInfo: [], rows: rows, }; - let connection = new azdata.connection.ConnectionProfile(); + let connection = new azdata.connection.ConnectionProfile(); let queryRunner = new QueryRunner(testContext.apiWrapper.object); testContext.queryProvider.runQueryAndReturn = () => { return Promise.resolve(result); }; testContext.apiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => testContext.queryProvider); @@ -217,12 +228,12 @@ describe('Query Runner', () => { let rows: azdata.DbCellValue[][] = []; let expected = false; - let result : azdata.SimpleExecuteResult = { + let result: azdata.SimpleExecuteResult = { rowCount: 2, columnInfo: [], rows: rows, }; - let connection = new azdata.connection.ConnectionProfile(); + let connection = new azdata.connection.ConnectionProfile(); let queryRunner = new QueryRunner(testContext.apiWrapper.object); testContext.queryProvider.runQueryAndReturn = () => { return Promise.resolve(result); }; testContext.apiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => testContext.queryProvider); @@ -242,12 +253,12 @@ describe('Query Runner', () => { ]; let expected = true; - let result : azdata.SimpleExecuteResult = { + let result: azdata.SimpleExecuteResult = { rowCount: 2, columnInfo: [], rows: rows, }; - let connection = new azdata.connection.ConnectionProfile(); + let connection = new azdata.connection.ConnectionProfile(); let queryRunner = new QueryRunner(testContext.apiWrapper.object); testContext.queryProvider.runQueryAndReturn = () => { return Promise.resolve(result); }; testContext.apiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => testContext.queryProvider); @@ -267,12 +278,12 @@ describe('Query Runner', () => { ]; let expected = false; - let result : azdata.SimpleExecuteResult = { + let result: azdata.SimpleExecuteResult = { rowCount: 2, columnInfo: [], rows: rows, }; - let connection = new azdata.connection.ConnectionProfile(); + let connection = new azdata.connection.ConnectionProfile(); let queryRunner = new QueryRunner(testContext.apiWrapper.object); testContext.queryProvider.runQueryAndReturn = () => { return Promise.resolve(result); }; testContext.apiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => testContext.queryProvider); @@ -286,12 +297,12 @@ describe('Query Runner', () => { let rows: azdata.DbCellValue[][] = []; let expected = false; - let result : azdata.SimpleExecuteResult = { + let result: azdata.SimpleExecuteResult = { rowCount: 2, columnInfo: [], rows: rows, }; - let connection = new azdata.connection.ConnectionProfile(); + let connection = new azdata.connection.ConnectionProfile(); let queryRunner = new QueryRunner(testContext.apiWrapper.object); testContext.queryProvider.runQueryAndReturn = () => { return Promise.resolve(result); }; testContext.apiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => testContext.queryProvider); diff --git a/extensions/machine-learning/src/typings/notebookServices.d.ts b/extensions/machine-learning/src/typings/notebookServices.d.ts index eb643b105e..9cb855abd7 100644 --- a/extensions/machine-learning/src/typings/notebookServices.d.ts +++ b/extensions/machine-learning/src/typings/notebookServices.d.ts @@ -38,6 +38,7 @@ export interface IJupyterServerInstallation { export interface IPackageDetails { name: string; version: string; + readonly?: boolean; } export interface IPackageTarget { diff --git a/extensions/notebook/src/types.d.ts b/extensions/notebook/src/types.d.ts index 984a47d7a5..bbf0a115e3 100644 --- a/extensions/notebook/src/types.d.ts +++ b/extensions/notebook/src/types.d.ts @@ -63,6 +63,7 @@ export interface IJupyterServerInstallation { export interface IPackageDetails { name: string; version: string; + readonly?: boolean; } /**