Is it possible to use variables with the 'DIALOG' and 'CONVERSATION' commands? - sql-server

I've been able to setup my Visual Studio (2019) Database Project up and using database references fine for tables, stored procedures, views, etc. They all seem to match up fine with the schema I've imported. However, I can't seem to avoid errors when it comes to referencing the same database's contracts, types, services, etc. When I reference the target database's service I have no issue (I assume this is because it accepts basic NVARCHAR strings versus a strongly-typed schema.
I've got the following example that just sends a message to another database's service. I get the error: "SQL71502: Procedure: [dbo].[z_Queue_SendMessage] has an unresolved reference to object [#VariableName]" with #VariableName being all of the following: (#RequestMessageType, #BasicContract, #InitiatorService)
CREATE PROCEDURE [dbo].[z_Queue_SendMessage]
#ProcedureName VARCHAR(1000) = 'Procedure',
#SubProcedureName VARCHAR(500) = 'SO',
#ProcedureType VARCHAR(200) = 'Delete',
#Inserted AS XML = NULL,
#Deleted AS XML = NULL
AS
DECLARE #InitDlgHandle UNIQUEIDENTIFIER;
DECLARE #RequestMsg NVARCHAR(MAX);
DECLARE #Message XML;
DECLARE #RequestMessageType NVARCHAR(100) = '//$(WebDb)' + N'/Queue/RequestMessage'
DECLARE #InitiatorService NVARCHAR(100) = '//$(WebDb)' + N'/Queue/InitiatorService'
DECLARE #BasicContract NVARCHAR(100) = '//$(WebDb)' + N'/Queue/BasicContract'
DECLARE #TargetService NVARCHAR(100) = '//$(WebDb)' + N'/Queue/TargetService'
BEGIN TRANSACTION;
BEGIN DIALOG #InitDlgHandle
FROM SERVICE [#InitiatorService] --Here
TO SERVICE #TargetService
ON CONTRACT [#BasicContract] --Here
WITH
ENCRYPTION = OFF;
SET #Message = (SELECT
ProcedureName = #ProcedureName,
SubProcedureName = #SubProcedureName,
ProcedureType = #ProcedureType,
Inserted = #Inserted,
Deleted = #Deleted
FOR XML PATH('Request'));
SEND ON CONVERSATION #InitDlgHandle
MESSAGE TYPE [#RequestMessageType] --Here
(#Message);
SELECT #Message AS SentRequestMsg;
COMMIT TRANSACTION;
Is there any way to format this so I don't get the error message without using Dynamic SQL? It defeats the purpose if I can't confirm it matches the database schema by suppressing errors or using dynamic SQL instead.

This works
BEGIN DIALOG CONVERSATION #Handle
FROM SERVICE #SvcName
TO SERVICE #TargetSvc, 'CURRENT DATABASE'
WITH ENCRYPTION = OFF
Try not quoting both #InitiatorService, #BasicContract and #RequestMessageType -- this just makes no sense.

I ended up having to import the creation scripts/files into my Visual Studio Database Project to get this all to work properly without giving me syntax errors.
I tried extracting the DACPAC with "Include application-scoped objects only" unchecked and "include extended properties" checked but was still getting the same issues I had before.
I added individual SQL items to my project for messages, services and contracts. Examples:
Service:
CREATE SERVICE [//Web/Queue/InitiatorService]
ON QUEUE DataQueue;
Contract:
CREATE CONTRACT [//Web/Queue/BasicContract]
(
[//Web/Queue/RequestMessage] SENT BY INITIATOR,
[//Web/Queue/ReplyMessage] SENT BY TARGET
)
Message Types:
CREATE MESSAGE TYPE [//Web/Queue/ReplyMessage]
VALIDATION = WELL_FORMED_XML;
CREATE MESSAGE TYPE [//Web/Queue/RequestMessage]
VALIDATION = WELL_FORMED_XML;
That allowed me to conform to the database schema still and avoid future errors. I did still reference the target service with a SQLCMD variable so I can use the same name except for the service in development vs production environments. Here's my stored procedure for sending messages now:
CREATE PROCEDURE [dbo].[z_Queue_SendMessage]
#ProcedureName VARCHAR(1000) = 'Procedure',
#SubProcedureName VARCHAR(500) = 'SO',
#ProcedureType VARCHAR(200) = 'Delete',
#Inserted AS XML = NULL,
#Deleted AS XML = NULL
AS
DECLARE #InitDlgHandle UNIQUEIDENTIFIER;
DECLARE #RequestMsg NVARCHAR(MAX);
DECLARE #Message XML;
DECLARE #TargetService NVARCHAR(100) = N'//$(WebDb)/Queue/TargetService'
BEGIN TRANSACTION;
BEGIN DIALOG #InitDlgHandle
FROM SERVICE [//Web/Queue/InitiatorService]
TO SERVICE #TargetService
ON CONTRACT [//Web/Queue/BasicContract]
WITH
ENCRYPTION = OFF;
SET #Message = (SELECT
ProcedureName = #ProcedureName,
SubProcedureName = #SubProcedureName,
ProcedureType = #ProcedureType,
Inserted = #Inserted,
Deleted = #Deleted
FOR XML PATH('Request'));
SEND ON CONVERSATION #InitDlgHandle
MESSAGE TYPE [//Web/Queue/RequestMessage]
(#Message);
SELECT #Message AS SentRequestMsg;
COMMIT TRANSACTION;

Related

SQL Server 2012 dynamic SQL - stored procedure - getting syntax error

I am writing scripts to generate stored procedures within a database whose current schema notation will be unknown (think shared hosting).
I have decided to use dynamic SQL within the stored procedures so that the web application can pass the database schema based on a user defined setting to the SQL Server in order for it to fire properly.
When I started writing the stored procedures, I noticed that dynamic SQL opens up a whole SQL injection problem I would not normally have so I re-wrote the procedure to combat this. However even though SQL allows me to run the script to generate the stored procedure, each time I try to run the test stored procedure, I get a syntax error
Incorrect syntax near the keyword 'WHERE'
I believe this is to do with the parameter for the schema but I am at a loss as to why this is not working? I am entering the value dbo for the schema.
/*
Name : usp_GetTestTicker
Description : returns test ticker
*/
if not exists (select * from dbo.sysobjects
where id = object_id(N'usp_GetTestTicker')
and OBJECTPROPERTY(id, N'IsProcedure') = 1)
BEGIN
DECLARE #sql as nvarchar(150)
SET #sql = 'CREATE procedure usp_GetTestTicker AS'
EXEC(#sql)
END
GO
ALTER PROCEDURE usp_GetTestTicker
#schema VARCHAR(25),
#TickerItemId INT
AS
SET NOCOUNT ON
BEGIN
DECLARE #sql_cmd NVARCHAR(MAX)
DECLARE #sql_params NVARCHAR(MAX)
SET #sql_cmd = N'SELECT * FROM #schema.TickerItem WHERE TickerItemId = #TickerItemId'
SET #sql_params = N'#schema VARCHAR(25), #TickerItemId INT'
EXEC sp_executesql #sql_cmd, #sql_params, #schema, #TickerItemId
END
GO
To prevent SQL injection, you will need to validate the schema against the sys.schemas table, e.g.
ALTER PROCEDURE usp_GetTestTicker
#schema NVARCHAR(25),
#TickerItemId INT
AS
BEGIN
SET NOCOUNT ON
IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = #schema)
BEGIN
-- throw an error here. Your web code will have to handle the error and report an invalid schema
END
ELSE
BEGIN
DECLARE #sql_cmd NVARCHAR(MAX), #sql_params NVARCHAR(MAX)
SET #sql_cmd = N'SELECT * FROM ' + #schema + '.TickerItem WHERE TickerItemId = #TickerItemId'
SET #sql_params = N'#TickerItemId INT'
EXEC sp_executesql #sql_cmd, #sql_params, #TickerItemId
END
END

Service Broker Internal Activation Poisoning - Where?

I am experiencing poison messages and I am not sure why.
My broker setup looks like this:
CREATE MESSAGE TYPE
[//DB/Schema/RequestMessage]
VALIDATION = WELL_FORMED_XML;
CREATE MESSAGE TYPE
[//DB/Schema/ReplyMessage]
VALIDATION = WELL_FORMED_XML;
CREATE CONTRACT [//DB/Schema/Contract](
[//DB/Schema/RequestMessage] SENT BY INITIATOR,
[//DB/Schema/ReplyMessage] SENT BY TARGET
)
CREATE QUEUE Schema.TargetQueue
CREATE SERVICE [//DB/Schema/TargetService]
ON QUEUE Schema.TargetQueue (
[//DB/Schema/Method3Contract]
)
CREATE QUEUE Schema.InitiatorQueue
CREATE SERVICE [//DB/Schema/InitiatorService]
ON QUEUE Schema.InitiatorQueue
Then I have my internal activation procedure:
CREATE PROCEDURE Schema.Import
AS
DECLARE #RequestHandle UNIQUEIDENTIFIER;
DECLARE #RequestMessage VARCHAR(8);
DECLARE #RequestMessageName sysname;
WHILE (1=1)
BEGIN
BEGIN TRANSACTION;
WAITFOR (
RECEIVE TOP(1)
#RequestHandle = conversation_handle,
#RequestMessage = message_body,
#RequestMessageName = message_type_name
FROM
Schema.TargetQueue
), TIMEOUT 5000;
IF (##ROWCOUNT = 0)
BEGIN
COMMIT TRANSACTION;
BREAK;
END
EXEC Schema.ImportStep1 #ID = #RequestMessage;
--EXEC Schema.ImportStep2 #ID = #RequestMessage;
END CONVERSATION #RequestHandle;
COMMIT TRANSACTION;
END
My activation is enabled by:
ALTER QUEUE Schema.TargetQueue
WITH
STATUS = ON,
ACTIVATION
( STATUS = ON,
PROCEDURE_NAME = Schema.Import,
MAX_QUEUE_READERS = 10,
EXECUTE AS SELF
)
I initiate this process with this stored procedure
CREATE PROCEDURE Schema.ImportStart
AS
BEGIN
DECLARE #ID VARCHAR(8);
DECLARE Cursor CURSOR FOR
SELECT ID FROM OtherDatabase.OtherSchema.ImportTable
EXCEPT
SELECT ID FROM Table
OPEN Cursor;
FETCH NEXT FROM Cursor INTO #ID;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #InitiateHandle UNIQUEIDENTIFIER;
DECLARE #RequestMessage VARCHAR(8);
BEGIN TRANSACTION;
BEGIN DIALOG
#InitiateHandle
FROM SERVICE
[//DB/Schema/InitiatorService]
TO SERVICE
N'//DB/Schema/TargetService'
ON CONTRACT
[//DB/Schema/Contract]
WITH
ENCRYPTION = OFF;
SELECT #RequestMessage = #ID;
SEND ON CONVERSATION
#InitiateHandle
MESSAGE TYPE
[//DB/Schema/RequestMessage]
(#RequestMessage);
COMMIT TRANSACTION;
FETCH NEXT FROM Cursor INTO #ID;
END
CLOSE Cursor;
DEALLOCATE Cursor;
END
So how this should work is:
I execute ImportStart
A message for each ID gets generated
Internal activation makes Import steps execute
Instead, I get poison messaging and the queue becomes disabled.
If however,
I I set Schema.TargetQue Activation to OFF
EXEC schema.ImportStart
EXEC schema.Import manually
It works fine.
Any insights anyone?
Well:
Your message types are defined as well_formed_xml, yet you send varchar(8) as a message body. Does it really work?
You use [//DB/Schema/Method3Contract] for the target queue, but do not define it. A misspelling, most likely.
You specify EXECUTE AS SELF in the queue activation. BOL says some mystical thing about this case:
SELF
Specifies that the stored procedure executes as the current user. (The database principal executing this ALTER QUEUE statement.)
I'm not really sure I understand the quoted statement, because it apparently contradicts with your experience. If it would be your user account, everything should have been fine, because you seem to have all permissions necessary to do the job.
So, just in case - who is the owner of the Schema schema? What permissions does this principal possess? And, if it's not you, who executes the alter queue statement (and why)?
Without access to logs, it's significantly more difficult to diagnose the problem, but I would start with creating a new user account with permissions identical to yours, setting it the owner of the Schema schema and then slowly working it down, revoking unnecessary permissions until it breaks. Assuming, of course, it will work at all.

Untrusted security context in signed activated procedure

I have an activated procedure for a service broker queue that queries a linked server. I have signed the procedure using the method found here. However, I continuously see the following message in the sql server logs:
The activated proc '[dbo].[TestProc]' running on queue 'DBName.dbo.TestReceiveQueue' output the following: 'Access to the remote server is denied because the current security context is not trusted.'
The strange thing is that I have several different activated procedures in the same database, signed by the same certificate, that also do linked server queries, and work fine. For some reason, this procedure refuses to.
Here's some code to (mostly) reproduce the problem. I've created the certificate and associated login already.
CREATE PROCEDURE TestProc
WITH EXECUTE AS OWNER
AS
BEGIN
SET NOCOUNT ON;
DECLARE #convHandle UNIQUEIDENTIFIER;
DECLARE #msgTypeName SYSNAME;
DECLARE #status TINYINT;
DECLARE #srvName NVARCHAR(512);
DECLARE #srvConName NVARCHAR(256);
DECLARE #msgTypeValidation AS NCHAR(2);
DECLARE #msgBody NVARCHAR(256);
DECLARE #cmd AS NVARCHAR(50);
RECEIVE TOP(1)
#convHandle = conversation_handle,
#msgTypeName = message_type_name,
#status = status,
#srvName = service_name,
#srvConName = service_contract_name,
#msgTypeValidation = validation,
#msgBody = CAST(message_body AS NVARCHAR(256))
FROM TestReceiveQueue;
--SELECT #convHandle, #msgBody
IF (##ROWCOUNT != 0)
BEGIN
SELECT * FROM openquery(LINKEDSERVERNAME, 'SELECT * FROM LINKEDSERVERDB.SCHEMA.TABLE')
END CONVERSATION #convHandle
END
END
GO
CREATE MESSAGE TYPE [TestMessageType] VALIDATION = NONE;
CREATE CONTRACT TestContract (TestMessageType SENT BY INITIATOR)
CREATE QUEUE [dbo].[TestReceiveQueue] With STATUS = ON, RETENTION = OFF, ACTIVATION (STATUS = ON, PROCEDURE_NAME = [dbo].[TestProc], MAX_QUEUE_READERS = 1, EXECUTE AS OWNER ), POISON_MESSAGE_HANDLING (STATUS = OFF) ON [PRIMARY]
CREATE QUEUE [dbo].[TestSendQueue] WITH STATUS = ON, RETENTION = OFF, POISON_MESSAGE_HANDLING (STATUS = OFF) ON [PRIMARY]
CREATE SERVICE [TestReceiveService] ON QUEUE [dbo].[TestReceiveQueue] (TestContract)
CREATE SERVICE [TestSendService] ON QUEUE [dbo].[TestSendQueue] (TestContract)
Drop Procedure TestProc
ADD SIGNATURE TO OBJECT::[TestProc]
BY CERTIFICATE [ServiceBrokerProcsCert]
WITH PASSWORD = 'PASSWORDHERE'
GO
Is there any way I can debug this further, to figure out why I'm getting this error? I've tried ssbdiagnose on the conversation and there aren't any configuration errors. I also tried logging the CURRENT_USER inside the activated sproc which came back as dbo.
When I mark the database as trustworthy, it works, of course (but that's what I'm trying to avoid).
If database is TRUSTWORTHY OFF procedure will run only in context of signing user, not its OWNER as you expect.
Assign linked server privileges to user assiociated with ServiceBrokerProcsCert, it is right user in which context signed activation procedure runs.

Querying outside of a transaction

I've got a Trigger (yes I know, yuk) that needs to check that a bunch of updates to another table has finished before sending out a notification.
I can't avoid having to do this in a Trigger like this due to the source database being 3rd party and being designed badly in my opinion.
The problem being is that the trigger has its own transaction and does not detect any changes to the table, as they are happening outside of it.
Is there any way to get around this?
In the code below, I'm getting values from the metricvalue table and creating xml with it. currently it doesn't get all the available records as the trigger is being called before all the metrics are written (there is no way to avoid this unfortunately). I was hoping to put in a .5 sec pause and then do the query, but because of the transaction problem this doesn't work.
code:
ALTER TRIGGER [dbo].[STR_MetricStatusChange]
ON [dbo].[MetricStatus]
FOR INSERT, UPDATE
AS
BEGIN
DECLARE #Runid UNIQUEIDENTIFIER;
DECLARE #run_number INT;
DECLARE #sample_number INT;
DECLARE #primary_step INT;
DECLARE #secondary_step int;
DECLARE #secondary_step_maths_done int;
DECLARE #old_secondary_step_maths_done int;
DECLARE #primary_step_maths_done int;
DECLARE #old_primary_step_maths_done int;
DECLARE #Message nvarchar(MAX);
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
-- Get the fields we need from the current MetricStatus record
--
SELECT #Runid = i.runid,
#run_number = r.Run_Number,
#sample_number = i.sample_number,
#primary_step = i.primary_step,
#secondary_step_maths_done = i.secondary_step_maths_done,
#primary_step_maths_done = i.primary_step_maths_done
FROM INSERTED i
LEFT JOIN dbo.Runs r ON r.Run_id = i.runid
-- Standard Metrics secondary step notification to Notification Queue
--
SELECT #Message = (SELECT (SELECT 0 AS MessageType FOR XML PATH (''), TYPE ),
(SELECT #Runid AS RunId, #run_number AS RunNumber, #sample_number AS SampleNumber, #primary_step AS PrimaryStep, #secondary_step AS SecondaryStep, GETDATE() AS [TimeStamp] FOR XML PATH('Details'), TYPE),
(SELECT
(SELECT mv.MetricValue_MetricId AS '#MetricId',
mv.MetricValue_Value AS Value
FROM dbo.MetricValues mv
WHERE MetricValue_RunId = #Runid and MetricValue_SampleNumber = #sample_number
for XML PATH('Metric'), TYPE)
FOR XML PATH('Metrics'),TYPE)
FOR XML PATH (''), ROOT('Notification'))
-- Standard Metrics secondary step notification to Maths Engine Queue
--
EXEC dbo.usp_SendNotification #Message, 'MathsEngine';
END

SQL broker queue is populating twice

I am using SQL broker to do some asynchronous tasks(In my case sending mails). But the problem I am having is the stored procedure which is run when the the value is inserted to the queue runs twice every time the XML message is passed from a Trigger to the queue.
I have a message type:
CREATE MESSAGE TYPE MailMessage
AUTHORIZATION dbo
VALIDATION = WELL_FORMED_XML
I have a contract:
CREATE CONTRACT MailContract
AUTHORIZATION dbo
(MailMessage SENT BY INITIATOR)
I have a Queue:
CREATE QUEUE dbo.MessageQueue
WITH STATUS=ON,
ACTIVATION (
PROCEDURE_NAME = MailExecuter ,
MAX_QUEUE_READERS = 1,
EXECUTE AS OWNER );
I have two services:
CREATE SERVICE MailSendActivator
AUTHORIZATION dbo
ON QUEUE dbo.MessageQueue (MailContract) ; // I have removed this the contract to make it a initiator but it did not worked out
-- Create target Service
CREATE SERVICE MailSendExec
AUTHORIZATION dbo
ON QUEUE dbo.MessageQueue (MailContract);
Here is my trigger:
CREATE TRIGGER MailSendTrigOnMailQueue ON dbo.MailQueue
FOR INSERT
As
SET NOCOUNT ON;
DECLARE #MessageBody XML
DECLARE #TableId int
SET #MessageBody = (SELECT CreatedDateTime,[Subject], MailType FROM inserted
FOR XML AUTO)
If (#MessageBody IS NOT NULL)
BEGIN
DECLARE #Handle UNIQUEIDENTIFIER;
BEGIN DIALOG CONVERSATION #Handle
FROM SERVICE MailSendActivator
TO SERVICE 'MailSendExec'
ON CONTRACT MailContract
WITH ENCRYPTION = OFF;
SEND ON CONVERSATION #Handle MESSAGE TYPE MailMessage(#MessageBody);
END
I have a stored procedure:
In the stored procedure I am Inserting values to a test table whether the stored procedure is running.
Stored Procedure:
CREATE PROCEDURE dbo.MailExecuter
AS
BEGIN
DECLARE #msgBody XML
DECLARE #dlgId uniqueidentifier
Insert into TestTable(Name, Test) values('MEX','test');
WHILE (1 = 1)
BEGIN
WAITFOR ( RECEIVE TOP(1) #msgBody = CAST(message_body AS XML), #dlgId = conversation_handle FROM dbo.MessageQueue ), TIMEOUT 500
IF (##ROWCOUNT = 0 OR #msgBody IS NULL)
BEGIN
BREAK
END
ELSE
BEGIN
DECLARE #Subject nvarchar(200), #CreatedDateTime datetime, #MailType nvarchar(50)
---EXEC dbo.SendMails 1,1;
END
END CONVERSATION #dlgId
END
END
But the stored procedure is running twice and populate my test table twice. I think the problem is with the send conversation part in the trigger.
I have been struck on this for a long time. Please, Can some one help me on this
You need to add to the RECEIVEprojection list the message_type_name. You must look at what message type you received and only call the mailing routine when the message type is MailMessage. As things are, you will always get a second message, the one from your END DIALOG. You simply need to call END DIALOG again on that case, to close the sending side handle:
WAITFOR (
RECEIVE TOP(1)
#msgBody = CAST(message_body AS XML),
#dlgId = conversation_handle ,
#msgType = message_type_name
FROM dbo.MessageQueue ), TIMEOUT 500
IF (##ROWCOUNT = 0 OR #msgBody IS NULL)
BREAK
ELSE IF #msgType = N'MailMessage'
BEGIN
DECLARE #Subject nvarchar(200), #CreatedDateTime datetime, #MailType nvarchar(50)
---EXEC dbo.SendMails 1,1;
END
END CONVERSATION #dlgId;
Any particular reason you are re-inventing the wheel? This is pretty much how sp_send_dbmail already works (except is using external activation).

Resources