Enable scripting for triggers and other objects (#16885)

* WIP 1

* Add parentName to azdata

* Add some additional types for scripting

* Add parent type name to support view subobjects

* bump dependencies and address review comments
This commit is contained in:
Karl Burtram
2021-08-26 14:43:36 -07:00
committed by GitHub
parent dc1e460f71
commit a0f3d0873b
11 changed files with 99 additions and 200 deletions

View File

@@ -12,6 +12,8 @@ export class ObjectMetadataWrapper implements ObjectMetadata {
public urn: string;
public name: string;
public schema: string;
public parentName: string;
public parentTypeName: string;
public get fullName(): string {
return `${this.schema}.${this.name}`;
@@ -23,6 +25,8 @@ export class ObjectMetadataWrapper implements ObjectMetadata {
this.urn = from.urn;
this.name = from.name;
this.schema = from.schema;
this.parentName = from.parentName;
this.parentTypeName = from.parentTypeName;
}
public matches(other: ObjectMetadataWrapper): boolean {
@@ -32,7 +36,8 @@ export class ObjectMetadataWrapper implements ObjectMetadata {
return this.metadataType === other.metadataType
&& this.schema === other.schema
&& this.name === other.name;
&& this.name === other.name
&& this.parentName === other.parentName;
}
// custom sort : Table > View > Stored Procedures > Function

View File

@@ -19,35 +19,45 @@ suite('Explorer Widget Tests', () => {
metadataTypeName: undefined,
urn: undefined,
name: 'testView',
schema: undefined
schema: undefined,
parentName: undefined,
parentTypeName: undefined
},
{
metadataType: MetadataType.Table,
metadataTypeName: undefined,
urn: undefined,
name: 'testTable',
schema: undefined
schema: undefined,
parentName: undefined,
parentTypeName: undefined
},
{
metadataType: MetadataType.SProc,
metadataTypeName: undefined,
urn: undefined,
name: 'testSProc',
schema: undefined
schema: undefined,
parentName: undefined,
parentTypeName: undefined
},
{
metadataType: MetadataType.Function,
metadataTypeName: undefined,
urn: undefined,
name: 'testFunction',
schema: undefined
schema: undefined,
parentName: undefined,
parentTypeName: undefined
},
{
metadataType: MetadataType.View,
metadataTypeName: undefined,
urn: undefined,
name: 'firstView',
schema: undefined
schema: undefined,
parentName: undefined,
parentTypeName: undefined
}
].map(m => new ObjectMetadataWrapper(m));
@@ -84,35 +94,45 @@ suite('Explorer Widget Tests', () => {
metadataTypeName: undefined,
urn: undefined,
name: 'testView',
schema: undefined
schema: undefined,
parentName: undefined,
parentTypeName: undefined
},
{
metadataType: MetadataType.Table,
metadataTypeName: undefined,
urn: undefined,
name: 'testTable',
schema: undefined
schema: undefined,
parentName: undefined,
parentTypeName: undefined
},
{
metadataType: MetadataType.SProc,
metadataTypeName: undefined,
urn: undefined,
name: 'testSProc',
schema: undefined
schema: undefined,
parentName: undefined,
parentTypeName: undefined
},
{
metadataType: MetadataType.Function,
metadataTypeName: undefined,
urn: undefined,
name: 'testFunction',
schema: undefined
schema: undefined,
parentName: undefined,
parentTypeName: undefined
},
{
metadataType: MetadataType.View,
metadataTypeName: undefined,
urn: undefined,
name: 'firstView',
schema: undefined
schema: undefined,
parentName: undefined,
parentTypeName: undefined
}
].map(o => new ObjectMetadataWrapper(o));
const filter = new ExplorerFilter('database', ['name']);

View File

@@ -1,158 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Disposable } from 'vs/base/common/lifecycle';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType';
import { isWindows } from 'vs/base/common/platform';
import { INodeContextValue } from 'sql/workbench/services/objectExplorer/browser/mssqlNodeContext';
export class NodeContextUtils extends Disposable {
static readonly canSelect = new Set([NodeType.Table, NodeType.View]);
static readonly canEditData = new Set([NodeType.Table]);
static readonly canCreateOrDelete = new Set([NodeType.AggregateFunction, NodeType.PartitionFunction, NodeType.ScalarValuedFunction,
NodeType.Schema, NodeType.StoredProcedure, NodeType.Table, NodeType.TableValuedFunction,
NodeType.User, NodeType.UserDefinedTableType, NodeType.View]);
static readonly canExecute = new Set([NodeType.StoredProcedure]);
static readonly canAlter = new Set([NodeType.AggregateFunction, NodeType.PartitionFunction, NodeType.ScalarValuedFunction,
NodeType.StoredProcedure, NodeType.TableValuedFunction, NodeType.View, NodeType.Function]);
// General node context keys
static NodeProvider = new RawContextKey<string>('nodeProvider', undefined);
static IsDatabaseOrServer = new RawContextKey<boolean>('isDatabaseOrServer', false);
static IsWindows = new RawContextKey<boolean>('isWindows', isWindows);
static IsCloud = new RawContextKey<boolean>('isCloud', false);
static NodeType = new RawContextKey<string>('nodeType', undefined);
static NodeLabel = new RawContextKey<string>('nodeLabel', undefined);
// Scripting context keys
static CanScriptAsSelect = new RawContextKey<boolean>('canScriptAsSelect', false);
static CanEditData = new RawContextKey<boolean>('canEditData', false);
static CanScriptAsCreateOrDelete = new RawContextKey<boolean>('canScriptAsCreateOeDelete', false);
static CanScriptAsExecute = new RawContextKey<boolean>('canScriptAsExecute', false);
static CanScriptAsAlter = new RawContextKey<boolean>('canScriptAsAlter', false);
private nodeProviderKey: IContextKey<string>;
private isCloudKey: IContextKey<boolean>;
private nodeTypeKey: IContextKey<string>;
private nodeLabelKey: IContextKey<string>;
private isDatabaseOrServerKey: IContextKey<boolean>;
private canScriptAsSelectKey: IContextKey<boolean>;
private canEditDataKey: IContextKey<boolean>;
private canScriptAsCreateOrDeleteKey: IContextKey<boolean>;
private canScriptAsExecuteKey: IContextKey<boolean>;
private canScriptAsAlterKey: IContextKey<boolean>;
constructor(
private nodeContextValue: INodeContextValue,
@IContextKeyService private contextKeyService: IContextKeyService,
@IConnectionManagementService private connectionManagementService: IConnectionManagementService,
@ICapabilitiesService private capabilitiesService: ICapabilitiesService
) {
super();
this.bindContextKeys();
// Set additional node context keys
if (this.nodeContextValue.node) {
const node = this.nodeContextValue.node;
if (node.payload) {
this.setNodeProvider();
this.setIsCloud();
if (node.type) {
this.setIsDatabaseOrServer();
this.nodeTypeKey.set(node.type);
} else if (node.contextValue && node.providerHandle === mssqlProviderName) {
this.setIsDatabaseOrServer();
this.setScriptingContextKeys();
this.nodeTypeKey.set(node.contextValue);
}
}
if (node.label) {
this.nodeLabelKey.set(node.label.label);
}
}
}
private bindContextKeys(): void {
this.isCloudKey = NodeContextUtils.IsCloud.bindTo(this.contextKeyService);
this.nodeTypeKey = NodeContextUtils.NodeType.bindTo(this.contextKeyService);
this.nodeLabelKey = NodeContextUtils.NodeLabel.bindTo(this.contextKeyService);
this.isDatabaseOrServerKey = NodeContextUtils.IsDatabaseOrServer.bindTo(this.contextKeyService);
this.canScriptAsSelectKey = NodeContextUtils.CanScriptAsSelect.bindTo(this.contextKeyService);
this.canEditDataKey = NodeContextUtils.CanEditData.bindTo(this.contextKeyService);
this.canScriptAsCreateOrDeleteKey = NodeContextUtils.CanScriptAsCreateOrDelete.bindTo(this.contextKeyService);
this.canScriptAsExecuteKey = NodeContextUtils.CanScriptAsExecute.bindTo(this.contextKeyService);
this.canScriptAsAlterKey = NodeContextUtils.CanScriptAsAlter.bindTo(this.contextKeyService);
this.nodeProviderKey = NodeContextUtils.NodeProvider.bindTo(this.contextKeyService);
}
/**
* Helper function to get the node provider
*/
private setNodeProvider(): void {
if (this.nodeContextValue.node.payload.providerName) {
this.nodeProviderKey.set(this.nodeContextValue.node.payload.providerName);
} else if (this.nodeContextValue.node.childProvider) {
this.nodeProviderKey.set(this.nodeContextValue.node.childProvider);
}
}
/**
* Helper function to tell whether a connected node is cloud or not
*/
private setIsCloud(): void {
const profile = new ConnectionProfile(this.capabilitiesService,
this.nodeContextValue.node.payload);
const connection = this.connectionManagementService.findExistingConnection(profile);
if (connection) {
const serverInfo = this.connectionManagementService.getServerInfo(connection.id);
if (serverInfo.isCloud) {
this.isCloudKey.set(true);
}
}
}
/**
* Helper function to tell whether a connected node is a database or a
* server or not. Added this key because this is easier to write than
* writing an OR statement in ContextKeyExpr
*/
private setIsDatabaseOrServer(): void {
const isDatabaseOrServer = (this.nodeContextValue.node.contextValue === NodeType.Server ||
this.nodeContextValue.node.contextValue === NodeType.Database ||
this.nodeContextValue.node.type === NodeType.Server ||
this.nodeContextValue.node.type === NodeType.Database);
this.isDatabaseOrServerKey.set(isDatabaseOrServer);
}
/**
* Helper function to get the correct context from node for showing
* scripting context menu actions
*/
private setScriptingContextKeys(): void {
const nodeType = this.nodeContextValue.node.contextValue;
if (NodeContextUtils.canCreateOrDelete.has(nodeType)) {
this.canScriptAsCreateOrDeleteKey.set(true);
}
if (NodeContextUtils.canEditData.has(nodeType)) {
this.canEditDataKey.set(true);
}
if (NodeContextUtils.canAlter.has(nodeType)) {
this.canScriptAsAlterKey.set(true);
}
if (NodeContextUtils.canExecute.has(nodeType)) {
this.canScriptAsExecuteKey.set(true);
}
if (NodeContextUtils.canSelect.has(nodeType)) {
this.canScriptAsSelectKey.set(true);
}
}
}

View File

@@ -103,8 +103,8 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
ConnectionContextKey.Provider.notEqualsTo('KUSTO'),
ConnectionContextKey.Provider.notEqualsTo('LOGANALYTICS'),
ContextKeyExpr.or(
TreeNodeContextKey.NodeType.isEqualTo('Table'),
TreeNodeContextKey.NodeType.isEqualTo('View')
TreeNodeContextKey.NodeType.isEqualTo(NodeType.Table),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.View)
)
)
});
@@ -117,8 +117,8 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
title: localize('scriptKustoSelect', "Take 10")
},
when: ContextKeyExpr.or(
ContextKeyExpr.and(ConnectionContextKey.Provider.isEqualTo('KUSTO'), TreeNodeContextKey.NodeType.isEqualTo('Table')),
ContextKeyExpr.and(ConnectionContextKey.Provider.isEqualTo('LOGANALYTICS'), TreeNodeContextKey.NodeType.isEqualTo('Table')))
ContextKeyExpr.and(ConnectionContextKey.Provider.isEqualTo('KUSTO'), TreeNodeContextKey.NodeType.isEqualTo(NodeType.Table)),
ContextKeyExpr.and(ConnectionContextKey.Provider.isEqualTo('LOGANALYTICS'), TreeNodeContextKey.NodeType.isEqualTo(NodeType.Table)))
});
MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
@@ -130,7 +130,7 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
},
when:
ContextKeyExpr.and(
TreeNodeContextKey.NodeType.isEqualTo('Table'),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.Table),
ConnectionContextKey.Provider.notEqualsTo('KUSTO'),
ConnectionContextKey.Provider.notEqualsTo('LOGANALYTICS'),
MssqlNodeContext.EngineEdition.notEqualsTo(DatabaseEngineEdition.SqlOnDemand.toString()),
@@ -150,16 +150,24 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
ConnectionContextKey.Provider.notEqualsTo('KUSTO'),
ConnectionContextKey.Provider.notEqualsTo('LOGANALYTICS'),
ContextKeyExpr.or(
TreeNodeContextKey.NodeType.isEqualTo('Table'),
TreeNodeContextKey.NodeType.isEqualTo('View'),
TreeNodeContextKey.NodeType.isEqualTo('Schema'),
ContextKeyExpr.and(TreeNodeContextKey.NodeType.isEqualTo('User'), MssqlNodeContext.EngineEdition.notEqualsTo(DatabaseEngineEdition.SqlOnDemand.toString())),
TreeNodeContextKey.NodeType.isEqualTo('UserDefinedTableType'),
TreeNodeContextKey.NodeType.isEqualTo('StoredProcedure'),
TreeNodeContextKey.NodeType.isEqualTo('AggregateFunction'),
TreeNodeContextKey.NodeType.isEqualTo('PartitionFunction'),
TreeNodeContextKey.NodeType.isEqualTo('ScalarValuedFunction'),
TreeNodeContextKey.NodeType.isEqualTo('TableValuedFunction')
TreeNodeContextKey.NodeType.isEqualTo(NodeType.Table),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.View),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.Schema),
ContextKeyExpr.and(TreeNodeContextKey.NodeType.isEqualTo(NodeType.User), MssqlNodeContext.EngineEdition.notEqualsTo(DatabaseEngineEdition.SqlOnDemand.toString())),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.User),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.UserDefinedTableType),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.StoredProcedure),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.AggregateFunction),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.PartitionFunction),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.ScalarValuedFunction),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.TableValuedFunction),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.Trigger),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.DatabaseTrigger),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.Index),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.Key),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.User),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.DatabaseRole),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.ApplicationRole)
)
)
});
@@ -172,8 +180,8 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
title: localize('scriptExecute', "Script as Execute")
},
when: ContextKeyExpr.or(
ContextKeyExpr.and(ConnectionContextKey.Provider.isEqualTo('MSSQL'), TreeNodeContextKey.NodeType.isEqualTo('StoredProcedure')),
ContextKeyExpr.and(ConnectionContextKey.Provider.isEqualTo('KUSTO'), TreeNodeContextKey.NodeType.isEqualTo('Function')))
ContextKeyExpr.and(ConnectionContextKey.Provider.isEqualTo('MSSQL'), TreeNodeContextKey.NodeType.isEqualTo(NodeType.StoredProcedure)),
ContextKeyExpr.and(ConnectionContextKey.Provider.isEqualTo('KUSTO'), TreeNodeContextKey.NodeType.isEqualTo(NodeType.Function)))
});
MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
@@ -231,7 +239,14 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
TreeNodeContextKey.NodeType.isEqualTo(NodeType.AggregateFunction),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.PartitionFunction),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.ScalarValuedFunction),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.TableValuedFunction)
TreeNodeContextKey.NodeType.isEqualTo(NodeType.TableValuedFunction),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.Trigger),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.DatabaseTrigger),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.Index),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.Key),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.User),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.DatabaseRole),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.ApplicationRole)
)
)
});

View File

@@ -26,7 +26,8 @@ export class MssqlNodeContext extends Disposable {
static readonly canEditData = new Set([NodeType.Table]);
static readonly canCreateOrDelete = new Set([NodeType.AggregateFunction, NodeType.PartitionFunction, NodeType.ScalarValuedFunction,
NodeType.Schema, NodeType.StoredProcedure, NodeType.Table, NodeType.TableValuedFunction,
NodeType.User, NodeType.UserDefinedTableType, NodeType.View]);
NodeType.User, NodeType.UserDefinedTableType, NodeType.View, NodeType.Trigger, NodeType.DatabaseTrigger,
NodeType.Index, NodeType.User, NodeType.DatabaseRole, NodeType.ApplicationRole, NodeType.Key]);
static readonly canExecute = new Set([NodeType.StoredProcedure, NodeType.Function]);
static readonly canAlter = new Set([NodeType.AggregateFunction, NodeType.PartitionFunction, NodeType.ScalarValuedFunction,
NodeType.StoredProcedure, NodeType.TableValuedFunction, NodeType.View, NodeType.Function]);

View File

@@ -407,15 +407,17 @@ suite('SQL Object Explorer Service tests', () => {
metadataTypeName: 'Database',
urn: '//server/db1/',
name: 'Db1',
schema: null
schema: undefined,
parentName: undefined,
parentTypeName: undefined
};
const databaseNode = new TreeNode(NodeType.Database, 'Db1', false, 'testServerName\\Db1', '', '', null, databaseMetaData, undefined, undefined);
const databaseNode = new TreeNode(NodeType.Database, 'Db1', false, 'testServerName\\Db1', '', '', undefined, databaseMetaData, undefined, undefined);
databaseNode.connection = connection;
databaseNode.session = objectExplorerSession;
const tablesNode = new TreeNode(NodeType.Folder, 'Tables', false, 'testServerName\\Db1\\tables', '', '', databaseNode, null, undefined, undefined);
const tablesNode = new TreeNode(NodeType.Folder, 'Tables', false, 'testServerName\\Db1\\tables', '', '', databaseNode, undefined, undefined, undefined);
databaseNode.children = [tablesNode];
const table1Node = new TreeNode(NodeType.Table, 'dbo.Table1', false, 'testServerName\\Db1\\tables\\dbo.Table1', '', '', tablesNode, null, undefined, undefined);
const table2Node = new TreeNode(NodeType.Table, 'dbo.Table2', false, 'testServerName\\Db1\\tables\\dbo.Table2', '', '', tablesNode, null, undefined, undefined);
const table1Node = new TreeNode(NodeType.Table, 'dbo.Table1', false, 'testServerName\\Db1\\tables\\dbo.Table1', '', '', tablesNode, undefined, undefined, undefined);
const table2Node = new TreeNode(NodeType.Table, 'dbo.Table2', false, 'testServerName\\Db1\\tables\\dbo.Table2', '', '', tablesNode, undefined, undefined, undefined);
tablesNode.children = [table1Node, table2Node];
assert.equal(table1Node.getSession(), objectExplorerSession);
assert.equal(table1Node.getConnectionProfile(), connection);
@@ -451,7 +453,9 @@ suite('SQL Object Explorer Service tests', () => {
metadataTypeName: 'Database',
urn: '//server/db1/',
name: 'Db1',
schema: undefined
schema: undefined,
parentName: undefined,
parentTypeName: undefined
};
const databaseName = 'Db1';
const databaseNode = new TreeNode(NodeType.Database, databaseName, false, 'testServerName\\Db1', '', '', undefined, databaseMetadata, undefined, undefined);