DDL Triggers and SET options

I have just had an unforgettable experience with DDL triggers and, as far as I see on Google search, I'm not alone.

DDL triggers, as you know, fire stored procedure in a response to server or database DDL events, like metadata or security changes. We use them to track changes on publishing server (those changes that replication cannot take care of) and apply them on subscriber. So, DDL triggers procedure writes statement into Statement table and other process later is executing them on subscriber.

Here is a code of DDL trigger and statements logging table below.

################################################################################

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

CREATE TABLE dbo.DDLOutput(

ID int IDENTITY(1,1) NOT NULL,

DateAdded datetime NULL,

EventType nvarchar(255) NULL,

DatabaseName sysname NULL,

SchemaName sysname NULL,

ObjectName sysname NULL,

ObjectType nvarchar(255) NULL,

Statement nvarchar(max) NULL,

LoginName sysname NULL,

CONSTRAINT PK_DDLOutput PRIMARY KEY NONCLUSTERED ( ID )

)

GO

################################################################################

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

CREATE TRIGGER [TRG_DDLServer]

ON ALL SERVER

FOR

DDL_EVENTS

AS

BEGIN TRY

DECLARE @PostTime datetime

DECLARE @EventType nvarchar(max)

DECLARE @DatabaseName nvarchar(max)

DECLARE @SchemaName nvarchar(max)

DECLARE @ObjectName nvarchar(max)

DECLARE @ObjectType nvarchar(max)

DECLARE @CommandText nvarchar(max)

DECLARE @LoginName nvarchar(max)

DECLARE @error nvarchar(2048)

SELECT

@PostTime = EVENTDATA().value('(/EVENT_INSTANCE/PostTime)[1]','datetime'),

@EventType = EVENTDATA().value('(/EVENT_INSTANCE/EventType)[1]','nvarchar(max)'),

@DatabaseName = EVENTDATA().value('(/EVENT_INSTANCE/DatabaseName)[1]', 'nvarchar(max)'),

@SchemaName = EVENTDATA().value('(/EVENT_INSTANCE/SchemaName)[1]','nvarchar(max)'),

@ObjectName = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]','nvarchar(max)'),

@ObjectType = EVENTDATA().value('(/EVENT_INSTANCE/ObjectType)[1]','nvarchar(max)'),

@CommandText = EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)'),

@LoginName = EVENTDATA().value('(/EVENT_INSTANCE/LoginName)[1]','nvarchar(max)')

INSERT INTO DDLOutput(DateAdded,EventType,DatabaseName,SchemaName,ObjectName,ObjectType,Statement,LoginName)

VALUES(@PostTime,@EventType,@DatabaseName,@SchemaName,@ObjectName,@ObjectType,@CommandText,@LoginName)

END TRY

BEGIN CATCH

SET @error = 'Failed execute DDL trigger:' + ERROR_MESSAGE()

ROLLBACK;

RAISERROR (@error,18,1) WITH LOG

END CATCH

GO

################################################################################

After table and DDL trigger creation, nice thing that is worth mentioning, is that you cannot drop or rename this table, you will get “Invalid object” error message. Only after disabling the trigger you can drop or rename it.

After Creating trigger and table try to execute the following statement

CREATE TABLE test1 ( i int)

GO

Now, you can see in the logging table all the details of your statements: script itself, loginname, database where the statement was executed and so on.

Now try to execute the following:

SET ANSI_PADDING OFF

GO

CREATE TABLE test2 ( i int)

GO

You will get error message:

Msg 3609, Level 16, State 2, Line 1

The transaction ended in the trigger. The batch has been aborted

Failed execute DDL trigger:SELECT failed because the following SET options have incorrect settings: 'ANSI_PADDING'. Verify that SET options are correct for use with indexed views and/or indexes on computed columns and/or filtered indexes and/or query notifications and/or XML data type methods and/or spatial index operations.

The reason for this failure ( I must admit that it took me hours to get it work properly) is because trigger is using XQuery and in order for use XQuery those settings must be on: QUOTED_IDENTIFIER, , ANSI_WARNINGS, ANSI_PADDINGS, ANSI_NULLS, CONCAT_NULL_YELDS_NULL. NUMERIC_ROUNDABORT must be off.

I have tried setting ANSI_DEFAULTS ON, which should include first four settings but, for some weird reason, trigger still fails if I do not explicitly add each setting separately.

Your trigger should look like that:

################################################################################

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

CREATE TRIGGER [TRG_DDLServer]

ON ALL SERVER

FOR

DDL_EVENTS

AS

BEGIN TRY

DECLARE @PostTime datetime

DECLARE @EventType nvarchar(max)

DECLARE @DatabaseName nvarchar(max)

DECLARE @SchemaName nvarchar(max)

DECLARE @ObjectName nvarchar(max)

DECLARE @ObjectType nvarchar(max)

DECLARE @CommandText nvarchar(max)

DECLARE @LoginName nvarchar(max)

DECLARE @error nvarchar(2048)

SET ANSI_PADDING ON

SET ANSI_NULLS ON

SET ANSI_WARNINGS ON

SET QUOTED_IDENTIFIER ON

SET CONCAT_NULL_YIELDS_NULL ON

SET NUMERIC_ROUNDABORT OFF

SELECT

@PostTime = EVENTDATA().value('(/EVENT_INSTANCE/PostTime)[1]','datetime'),

@EventType = EVENTDATA().value('(/EVENT_INSTANCE/EventType)[1]','nvarchar(max)'),

@DatabaseName = EVENTDATA().value('(/EVENT_INSTANCE/DatabaseName)[1]', 'nvarchar(max)'),

@SchemaName = EVENTDATA().value('(/EVENT_INSTANCE/SchemaName)[1]','nvarchar(max)'),

@ObjectName = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]','nvarchar(max)'),

@ObjectType = EVENTDATA().value('(/EVENT_INSTANCE/ObjectType)[1]','nvarchar(max)'),

@CommandText = EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)'),

@LoginName = EVENTDATA().value('(/EVENT_INSTANCE/LoginName)[1]','nvarchar(max)')

INSERT INTO DDLOutput(DateAdded,EventType,DatabaseName,SchemaName,ObjectName,ObjectType,Statement,LoginName)

VALUES(@PostTime,@EventType,@DatabaseName,@SchemaName,@ObjectName,@ObjectType,@CommandText,@LoginName)

END TRY

BEGIN CATCH

SET @error = 'Failed execute DDL trigger:' + ERROR_MESSAGE()

ROLLBACK;

RAISERROR (@error,18,1) WITH LOG

END CATCH

GO

################################################################################

Comments

Popular posts from this blog

SQL Awesomeness: Finding a QUALIFY query clause in the depths of the database ocean

Look back and realize how far you came

The Greatest Reasons to use or not to use a Centralized Data Access Architecture