DDL Triggers Implementation for Repository-like Solution to User Objects Source Versioning

7

Click here to load reader

Transcript of DDL Triggers Implementation for Repository-like Solution to User Objects Source Versioning

Page 1: DDL Triggers Implementation for Repository-like Solution to User Objects Source Versioning

DDL Triggers implementation for repository-like solution to user objects source versioningAuthor: Federico Marzullo ([email protected])Date: 03/25/2011Works under: Microsoft SQL Server 2005 or aboveFull script file:

Introduction:The objective of this asset is to easily show the implementation of DDL Triggers as a feasible option for DDL code versioning. How many times we ended up on situations like: “I dropped the wrong View” or “Oooops, I wasn’t supposed to commit those changes to this Stored Procedure yet” or “Hey, till the last change done to this Function everything was working perfectly fine, what’s different?”??? Every SQL Server developer knows that here there’s no such thing as the “Ctrl+Z” (undo) once you have made DDL changes like the above, right?

Well, these script chunks will allow you to have a nice workaround in a few steps.

In addition, the scripts below include object’s appropriate granting for selection or execution depending on the object type.

What really is a DDL trigger anyway?

One thing that most of the times scare more than one DBA out of their pants is the trigger concept. There is this well known sort of panic related to the risk of performance leaks and locks around them, which is actually pretty accurate; working with triggers isn’t a thing to be taken slightly into consideration.

Until SQL Server 2005 the only triggers existing were DML (Data Manipulation Language) type, that means only for transactional operations: INSERT, UPDATE, DELETE…

But DDL triggers are a whole different thing… Even when at the beginning they behave the same way as the DML triggers do, these ones works with Server and Database events, but this time being called thru: CREATE, UPDATE, DROP commands (remember DDL stands for Data Definition Language).

Below you’ll find a reference chart, containing both Server and Database related events that can be applied on a DDL Trigger implementation:

1

Page 2: DDL Triggers Implementation for Repository-like Solution to User Objects Source Versioning

2

Page 3: DDL Triggers Implementation for Repository-like Solution to User Objects Source Versioning

For practical and illustrative purposes, this asset will use only Tables, Stored Procedures, Functions and Views related events.

How to:

1. Create the table structures needed to support the implementation

-- this table will hold the objects versioning, one row per version per object as they're created/updated/droppedCREATE TABLE dbo.AdministratorLog(AdministratorLogID int IDENTITY(1, 1) NOT NULL,

EventType varchar(50) NULL,ObjectName varchar(256) NULL,ObjectType varchar(25) NULL,SQLCommand text NULL,LoginName varchar(256) NULL,[TimeStamp] datetime NOT NULL)

GO

-- now we need to set the proper constraints, indexes and defaults for the table columnsALTER TABLE dbo.AdministratorLog ADD CONSTRAINT

DF_AdministratorLog_TimeStamp DEFAULT getdate() FOR TimeStampGO

ALTER TABLE dbo.AdministratorLog ADD CONSTRAINT PK_AdministratorLog PRIMARY KEY CLUSTERED (AdministratorLogID) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

GO

CREATE NONCLUSTERED INDEX IX_AdministratorLog_EventType ON dbo.AdministratorLog(EventType) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,

ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]GO

CREATE NONCLUSTERED INDEX IX_AdministratorLog_ObjectName ON dbo.AdministratorLog(ObjectName) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,

ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]GO

CREATE NONCLUSTERED INDEX IX_AdministratorLog_ObjectType ON dbo.AdministratorLog(ObjectType) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,

ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

GO

-- standard parameters table, this time used to keep track on the users you want to give grants to on the given object. In this script its use is intended to keep the code dinamically alive as the application evolvesCREATE TABLE dbo.Parameters(ParameterID int IDENTITY(1, 1) NOT NULL,

Parameter varchar(50) NULL,Param_Value varchar(250) NULL)

GO

3

Page 4: DDL Triggers Implementation for Repository-like Solution to User Objects Source Versioning

-- now we need to set the proper index as beforeALTER TABLE dbo.Parameters ADD CONSTRAINT PK_Parameters PRIMARY KEY CLUSTERED

(ParameterID) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

GO

2. The DDL Trigger implementation

-- variables declarationDECLARE @dbname varchar(50), @grants varchar(500), @strSQL nvarchar(500)

SELECT @dbname = DB_NAME() --the current DB where the script is being executedSET @grants = 'webapp_user, webapp_job' -- the DB users to which grants will be given on objects creations

-- now we insert the corresponding parameter into the tableINSERT INTO dbo.Parameters(Parameter, Param_Value) VALUES('DDL Triggers - Grants', @grants)

SET @strSQL = 'GRANT SELECT, INSERT ON dbo.AdministratorLog TO ' + @grants

EXEC sp_executesql @strSQL

-- now we state the trigger creation and its eventsCREATE TRIGGER DDL_TRG_Admin_SourceControl_Grants ON DATABASEFOR CREATE_PROCEDURE, ALTER_PROCEDURE, DROP_PROCEDURE,

CREATE_TABLE, ALTER_TABLE, DROP_TABLE,CREATE_FUNCTION, ALTER_FUNCTION, DROP_FUNCTION,CREATE_VIEW, ALTER_VIEW, DROP_VIEW

AS

SET NOCOUNT ON

DECLARE @data xml, @ObjectName nvarchar(256), @EventType varchar(50), @ObjectType nvarchar(25), @grantsTo nvarchar(256), @strSQL nvarchar(500)SET @data = eventdata()

-- now we insert the user object’s info with all its contentINSERT INTO dbo.AdministratorLog(EventType, ObjectName, ObjectType, SQLCommand, LoginName)

VALUES(@data.value('(/EVENT_INSTANCE/EventType)[1]', 'varchar(50)'),@data.value('(/EVENT_INSTANCE/ObjectName)[1]', 'varchar(256)'), @data.value('(/EVENT_INSTANCE/ObjectType)[1]', 'varchar(25)'), @data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'varchar(max)'), @data.value('(/EVENT_INSTANCE/LoginName)[1]', 'varchar(256)'))

-- getting the event typeSET @EventType = eventdata().value('(/EVENT_INSTANCE/EventType)[1]', 'varchar(50)')

4

Page 5: DDL Triggers Implementation for Repository-like Solution to User Objects Source Versioning

-- if the event type is “CREATE” a new user object has been created and therefore corresponding grants should be givenIF left(@EventType, charindex('_', @EventType) - 1) = 'CREATE' BEGIN

SET @ObjectName = eventdata().value('(/EVENT_INSTANCE/ObjectName)[1]', 'nvarchar(256)')

SET @ObjectType = eventdata().value('(/EVENT_INSTANCE/ObjectType)[1]', 'nvarchar(25)')

-- getting the users to whom the grants will be assignedSELECT @grantsTo = Param_Value FROM dbo.parameters WHERE Parameter = 'DDL

Triggers - Grants'

IF @ObjectType = 'FUNCTION'SET @strSQL = 'EXECUTE ON dbo.' + @ObjectName

IF @ObjectType = 'PROCEDURE'SET @strSQL = 'EXECUTE ON dbo.' + @ObjectName

IF @ObjectType = 'TABLE'SET @strSQL = 'SELECT, INSERT, UPDATE, DELETE ON dbo.' + @ObjectName

IF @ObjectType = 'VIEW'SET @strSQL = 'SELECT ON dbo.' + @ObjectName

--SQL GRANTSSET @strSQL = 'GRANT ' + @strSQL + ' TO ' + @grantsTo

--EXEC SQLEXEC sp_executesql @strSQL

END

GO

3. Preload the Log table as version 0 for every user object in the DB

The aim of the script below is to preload the repository table, working as “version 0” for every user object in the DB. Once executed, the table will have one row per user object

-- populate the table with the last version of every object in the DB so farINSERT INTO dbo.AdministratorLog(EventType, ObjectName, ObjectType, SQLCommand, LoginName)SELECT CASE SO.xType WHEN 'P' THEN 'CREATE_PROCEDURE' WHEN 'FN' THEN 'CREATE_FUNCTION' WHEN 'V' THEN 'CREATE_VIEW' END,

SO.name, CASE SO.xType WHEN 'P' THEN 'PROCEDURE' WHEN 'FN' THEN 'FUNCTION' WHEN 'V' THEN 'VIEW' END,

SC.[Text], 'user_that_created_the_version'FROM sys.syscomments SCINNER JOIN sys.sysobjects SO ON SO.id = SC.idWHERE SO.xType IN('P', 'FN', 'V', 'U') -- filter Procedures, Functions, Views and TablesAND NOT SO.Name LIKE('dt_%') -- remove server objects from selectionAND NOT SO.Name LIKE('sys%') -- remove server objects from selection

5

Page 6: DDL Triggers Implementation for Repository-like Solution to User Objects Source Versioning

4. Future “nice to have” upgrades

So far I haven’t found a way to keep blanks and tabs on the sources, the repository stores the scripts perfectly fine but removes those chars. If you find some workaround on that, please let me know.

6