I'd like to turn trigger recursion on/off in my SQL CLR trigger. According to http://www.devx.com/tips/Tip/30031, I have to call
EXEC sp_dboption '<name of db>', 'recursive triggers', 'true'/'false'
Is there a way to get to know what the current DB name is? When creating trigger, I ask users to choose one, but I don't want to write it in a table.
Regards,
There is a very simple way to find the name of the database in which the SQLCLR Trigger is being fired: just make a connection to the Context Connection and get the Database property. You don't even need to execute a query :-).
The following should work in all SQLCLR object types (Stored Procedure, Function, User-Defined Aggregate, User-Defined Type, and Trigger):
string _DatabaseName;
using (SqlConnection _Connection = new SqlConnection("Context Connection = true;"))
{
_Connection.Open();
_DatabaseName = _Connection.Database;
}
That's it! I just tried it in a SQLCLR Trigger and it works great.
Another thing to keep in mind for limiting Triggers firing other Triggers is the TRIGGER_NESTLEVEL function. This works better in T-SQL Triggers where the value of ##PROCID is available and contains the [object_id] of the Trigger. So in T-SQL Triggers you can limit the recursion of each trigger individually but still allow Triggers to fire other Triggers on other Tables.
In SQLCLR it can still be used, but without the name of the Trigger you can only limit all Triggers. Meaning, you can prevent any Trigger from firing any other Trigger on any Table, including on the same Table, but there is no way to limit the firing of only that same Trigger while allowing Triggers on other tables that might be modified by the Trigger in question. Just use a Context Connection and run SELECT TRIGGER_NESTLEVEL(); via SqlCommand.ExecuteScalar().
You know what the database is when you create the trigger...
CREATE TRIGGER etc
....
GO
DECLARE #db varchar(100)
SET #db = DB_NAME()
EXEC sp_dboption #db, 'recursive triggers', 'true'/'false'
I've found a better solution.
I have to avoid calling EXEC sp_dboption at all. Instead, I have to create a temp table as a flag "no recursion", then check existing of the table at the beginning of the trigger and exit, if the table exists.
Why temporary table?
It's being killed at the end of the session. No need to reset the flag (in exceptional situation), which is necessary otherwise to avoid trigger being off permanently.
AFAIK, it's being created and killed independently for every connection. So, if the user changes data the same time, there will be no conflict (which is inevitable for EXEC sp_dboption).
Related
I am attempting to prevent usage of the default schema of "dbo" in my SQL Server databases. This is being applied to an existing long term project with ongoing maintenance where the developers also manage the SQL Server (are all sysadmin).
This is for the main reason to allow better dependency tracking between code and the SQL Server objects so that we can slowly migrate to a better naming convention. Eg. "dbo.Users", "dbo.Projects", "dbo.Categories" in a DB are nearly impossible to find in code once created because the "dbo." is often left out of SQL Syntax.
However a proper defined schema requires the usage in code. Eg. "Tracker.Users", "Tracker.Projects", etc ...
Even though we have standards set to not use "dbo" for objects it is still accidentally occurring due to management/business pressures for speed to develop.
Note: I'm creating this question simply to provide a solution someone else can find useful
EDIT: As pointed out, for non-sysadmin users the security option stated is a viable solution, however the DDL Trigger solution will also work on sysadmin users. The case for many small teams who have to manage there own boxes.
I feel like it would be 10,000 times simpler to just DENY ALTER on the dbo schema:
DENY ALTER ON SCHEMA::dbo TO [<role(s)/user(s)/group(s)>];
That's not too handy if everyone connects as sa but, well, fix that first.
The following Database DLL Trigger causes error feedback in both the SQL Manager GUI and via Manual TSQL code attempts to create an object for the types specified.
It includes a means to have a special user and provides clear feedback to the user attempting the object creation. It also works to raise the error with users who are sysadmin.
It does not affect existing objects unless the GUI/SQL tries to DROP and CREATE an existing "dbo" based object.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [CREATE_Prevent_dbo_Usage_2] ON DATABASE
FOR CREATE_TABLE, CREATE_VIEW, CREATE_PROCEDURE, CREATE_FUNCTION
AS
DECLARE #E XML = EVENTDATA();
DECLARE #CurrentDB nvarchar(200)=#E.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'NVARCHAR(2000)');
DECLARE #TriggerFeedbackName nvarchar(max)=#CurrentDB+N'.CREATE_Prevent_dbo_Usage'; -- used to feedback the trigger name on a failure so the user can disable it (or know where the issue is raised from)
DECLARE #temp nvarchar(2000)='';
DECLARE #SchemaName nvarchar(2000)=#E.value('(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(2000)');
DECLARE #ObjectName nvarchar(2000)=#E.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(2000)');
DECLARE #LoginName nvarchar(2000)=#E.value('(/EVENT_INSTANCE/LoginName)[1]', 'NVARCHAR(2000)');
DECLARE #CurrentObject nvarchar(200)=''; -- Schema.Table
IF #LoginName NOT IN ('specialUser') BEGIN -- users to exclude in evaluation.
IF CASE WHEN #SchemaName IN ('dbo') THEN 1 ELSE 0 END = 1 BEGIN -- is a DBO attempt to create.
SET #CurrentObject = #SchemaName+'.'+#ObjectName; -- grouped here for easy cut and paste/modify.
SET #temp='Cannot create "'+#CurrentObject+'".
This Database "'+#CurrentDB+'" has had creation of "dbo" objects restricted to improve code maintainability.
Use an existing schema or create a new one to group the purpose of the objects/code.
Disable this Trigger TEMPORARILY if you need to do some advanced manipulation of it.
(This message was produced by "'+#TriggerFeedbackName+'")';
throw 51000,#temp,1;
END
END
GO
ENABLE TRIGGER [CREATE_Prevent_dbo_Usage] ON DATABASE
GO
Does SQL Server allow triggers on extended properties, table level or column level?
If not, is there anything like a trigger that can detect an 'add' or 'update' on extended properties and execute a stored procedure automatically?
Thank you!
You can use a DDL trigger for this.
CREATE TRIGGER foo
ON DATABASE
FOR CREATE_EXTENDED_PROPERTY, ALTER_EXTENDED_PROPERTY
AS
BEGIN
/*TODO: Something useful here*/
SELECT EVENTDATA()
END
I am new to triggers and cursors and would like to understand what a particular trigger is doing. Here is the Trigger.
CREATE TRIGGER [dbo].[trg_XOnUpdate]
ON TableX
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #XID INT, #XKey NVARCHAR(33)
DECLARE updated_cursor CURSOR FOR
SELECT XID, XKey
FROM INSERTED
WHERE XStatus
IN ('AA', 'BB', 'CC')
OPEN updated_cursor
FETCH NEXT FROM updated_cursor INTO #XID, #XKey
WHILE ##FETCH_STATUS = 0
BEGIN
EXECUTE usp_UpdateXData #XID, #XKey
FETCH NEXT FROM updated_cursor INTO #XID, #XKey
END
CLOSE updated_cursor
DEALLOCATE updated_cursor
END
Understanding the Basic of Triggers
A trigger is an operation that is executed when some kind of event occurs to the database. It can be a data or object change. Listed below are the different types of tiggers:
Types of Triggers
DML(data manipulation language) triggers (SQL Server 2000- 80.0)
DDL(data definition language) triggers (SQL Server 2005- 90.0)
SQLCLR triggers (SQL Server 2005- 90.0)
Rules of Triggers
cannot create or modify Database objects using triggers
cannot perform any administrative tasks
cannot pass any kind of parameters
cannot directly call triggers
Advantage of Triggers
Triggers are useful for auditing data changes or auditing database as well as managing business rules. Below are some examples:
Triggers can be used to enforce referential integrity (For example you may not be able to apply foreign keys)
Can access both new values and old values in the database when going to do any insert, update or delete
Drill down further about triggers...
Understanding the Basic of Triggers
Understanding SQL Server inserted and deleted tables for DML triggers
I'm trying to have a trigger set up in an arbitrary database that will store information in a specific database on execution. However, I found that if the trigger is triggered by someone without explicit access to that database, the trigger execution will fail.
I was hoping to find away around this using:
CREATE TRIGGER [myTrigger]
on database
with execute as 'UserWithPermissions'
but that doesn't seem to work either... Does anyone know if this is possible? Any pointers would be greatly appreciated. Thanks.
In MSSQL Management go to DB-name => Security => Users and make sure the user has access. Also, Windows Network Authentication helps a lot when running procs from other machines
-- edit --
I think what you want is called 'EXECUTE AS'.
CREATE PROCEDURE dbo.usp_Demo
WITH EXECUTE AS 'SqlUser1'
AS
SELECT user_name(); -- Shows execution context is set to SqlUser1.
EXECUTE AS CALLER;
SELECT user_name(); -- Shows execution context is set to SqlUser2, the caller of the module.
REVERT;
SELECT user_name(); -- Shows execution context is set to SqlUser1.
GO
I am working with SQL Server 2005 and I have trigger on a table that will copy an deletions into another table. I cannot remove this trigger completely. My problem is that we have now developed an archiving strategy for this table. I need a way of "pausing" a trigger when the stored proc that does the archiving runs.
A little more detail would be useful on how the procedure is accessing the data, but assuming you are just getting the data, then deleting it from the table and wish to disable the trigger for this process, you can do the following
DISABLE TRIGGER trg ON tbl;
then
ENABLE TRIGGER trg ON tbl;
for the duration of the procedure.
This only works for SQL 2005+
An alternative method is to use Context_Info to disable it for a single session, while allowing other sessions to continue to fire the trigger.
Context_Info is a variable which belongs to the session. Its value can be changed using SET Context_Info.
The trigger will mostly look like this:
USE AdventureWorks;
GO
-- creating the table in AdventureWorks database
IF OBJECT_ID('dbo.Table1') IS NOT NULL
DROP TABLE dbo.Table1
GO
CREATE TABLE dbo.Table1(ID INT)
GO
-- Creating a trigger
CREATE TRIGGER TR_Test ON dbo.Table1 FOR INSERT,UPDATE,DELETE
AS
DECLARE #Cinfo VARBINARY(128)
SELECT #Cinfo = Context_Info()
IF #Cinfo = 0x55555
RETURN
PRINT 'Trigger Executed'
-- Actual code goes here
-- For simplicity, I did not include any code
GO
If you want to prevent the trigger from being executed you can do the following:
SET Context_Info 0x55555
INSERT dbo.Table1 VALUES(100)
Before issuing the INSERT statement, the context info is set to a value. In the trigger, we are first checking if the value of context info is the same as the value declared. If yes, the trigger will simply return without executing its code, otherwise the trigger will fire.
source: http://www.mssqltips.com/tip.asp?tip=1591
if DISABLE TRIGGER/ENABLE TRIGGER is not an option for some reason, you can create a table with a single row which will serve as a flag for the trigger.