Ok, this is a curly one. I'm working on some Delphi code that I didn't write, and I'm encountering a very strange problem. One of my stored procedures' parameters is coming through as null, even though it's definitely being sent 1.
The Delphi code uses a TADOQuery to execute the stored procedure (anonymized):
ADOQuery1.SQL.Text := "exec MyStoredProcedure :Foo,:Bar,:Baz,:Qux,:Smang,:Jimmy";
ADOQuery1.Parameters.ParamByName("Foo").Value := Integer(someFunction());
// other parameters all set similarly
ADOQuery1.ExecSQL;
Integer(SomeFunction()) currently always returns 1 - I checked with the debugger.
However, in my stored proc ( altered for debug purposes ):
create procedure MyStoredProcedure (
#Foo int, #Bar int, #Baz int,
#Qux int, #Smang int, #Jimmy varchar(20)
) as begin
-- temp debug
if ( #Foo is null ) begin
insert into TempLog values ( "oh crap" )
end
-- do the rest of the stuff here..
end
TempLog does indeed end up with "oh crap" in it (side question: there must be a better way of debugging stored procs: what is it?).
Here's an example trace from profiler:
exec [MYDB]..sp_procedure_params_rowset N'MyStoredProcedure',1,NULL,NULL
declare #p3 int
set #p3=NULL
exec sp_executesql
N'exec MyStoredProcedure #P1,#P2,#P3,#P4,#P5,#P6',
N'#P1 int OUTPUT,#P2 int,#P3 int,#P4 int,#P5 int,#P6 int',
#p3 output,1,1,1,0,200
select #p3
This looks a little strange to me. Notice that it's using #p3 and #P3 - could this be causing my issue?
The other strange thing is that it seems to depend on which TADOConnection I use.
The project is a dll which is passed a TADOConnection from another application. It calls all the stored procedures using this connection.
If instead of using this connection, I first do this:
ConnectionNew := TADOQuery.Create(ConnectionOld.Owner);
ConnectionNew.ConnectionString := ConnectionOld.ConnectionString;
TADOQuery1.Connection := ConnectionNew;
Then the issue does not occur! The trace from this situation is this:
exec [MYDB]..sp_procedure_params_rowset N'MyStoredProcedure',1,NULL,NULL
declare #p1 int
set #p1=64
exec sp_prepare #p1 output,
N'#P1 int,#P2 int,#P3 int,#P4 int,#P5 int,#P6 varchar(20)',
N'exec MyStoredProcedure #P1,#P2,#P3,#P4,#P5,#P6',
1
select #p1
SET FMTONLY ON exec sp_execute 64,0,0,0,0,0,' ' SET FMTONLY OFF
exec sp_unprepare 64
SET NO_BROWSETABLE OFF
exec sp_executesql
N'exec MyStoredProcedure #P1,#P2,#P3,#P4,#P5,#P6',
N'#P1 int,#P2 int,#P3 int,#P4 int,#P5 int,#P6 varchar(20)',
1,1,1,3,0,'400.00'
Which is a bit much for lil ol' me to follow, unfortunately. What sort of TADOConnection options could be influencing this?
Does anyone have any ideas?
Edit: Update below (didn't want to make this question any longer :P)
In my programs, I have lots of code very similar to your first snippet, and I haven't encountered this problem.
Is that actually your code, or is that how you've represented the problem for us to understand? Is the text for the SQL stored in your DFM or populated dynamically?
I was wondering if perhaps somehow the Params property of the query had already got a list of parameters defined/cached, in the IDE, and that might explain why P1 was being seen as output (which is almost certainly causing your NULL problem).
Just before you set the ParamByName.Value, try
ParamByName("Foo").ParamType=ptInput;
I'm not sure why you changing the connection string would also fix this, unless it's resetting the internal sense of the parameters for that query.
Under TSQLQuery, the Params property of a query gets reset/recreated whenever the SQL.Text value is changed (I'm not sure if that's true for a TADOQuery mind you), so that first snippet of yours ought to have caused any existing Params information to have been dropped.
If the 'ParamByname.ParamType' suggestion above does fix it for you, then surely there's something happening to the query elsewhere (at create-time? on the form?) that is causing it to think Foo is an output parameter...
does that help at all? :-)
caveat: i don't know delphi, but this issue rings a faint bell and so i'm interested in it
do you get the same result if you use a TADOStoredProc instead of a TADOQuery? see delphi 5 developers guide
also, it looks like the first trace does no prepare call and thinks #P1 is an output paramer in the execute, while the second trace does a prepare call with #P1 as an output but does not show #P1 as an output in the execute step - is this significant? it does seem odd, and so may be a clue
you might also try replacing the function call with a constant 1
good luck, and please let us know what you find out!
I suspect you have some parameters mismatch left over from the previous use of your ADOQuery.
Have you tried to reset your parameters after changing the SQL.Text:
ADOQuery1.Parameters.Refresh;
Also you could try to clear the parameters and explicitly recreate them:
ADOQuery1.Parameters.Clear;
ADOQuery1.Parameters.CreateParameter('Foo', ftInteger, pdInput, 0, 1);
[...]
I think changing the connection actually forces an InternalRefresh of the parameters.
ADOQuery1.Parameters.ParamByName("Foo").Value = Integer(someFunction());
Don't they use := for assignment in Object Pascal?
#Constantin
It must be a typo from the Author of the question.
#Blorgbeard
Hmmm... When you change SQL of a TADOQuery, is good use to
clear the parameters and recreate then using CreateParameter.
I would not rely on ParamCheck in runtime - since it leaves
the parameters' properties mostly undefined.
I've had such type of problem when relying on ParamCheck to
autofill the parameters - is rare but occurs.
Ah, if you go the CreateParameter route, create as first
parameter the #RETURN_VALUE one, since it'll catch the returned
value of the MSSQL SP.
The only time I've had a problem like this was when the DB Provider couldn't distinguish between Output (always sets it to null) and InputOutput (uses what you provide) parameters.
Ok, progress is made.. sort of.
#Robsoft was correct, setting the parameter direction to pdInput fixed the issue.
I traced into the VCL code, and it came down to TParameters.InternalRefresh.RefreshFromOleDB. This function is being called when I set the SQL.Text. Here's the (abridged) code:
function TParameters.InternalRefresh: Boolean;
procedure RefreshFromOleDB;
// ..
if OLEDBParameters.GetParameterInfo(ParamCount, PDBPARAMINFO(ParamInfo), #NamesBuffer) = S_OK then
for I := 0 to ParamCount - 1 do
with ParamInfo[I] do
begin
// ..
Direction := dwFlags and $F; // here's where the wrong value comes from
// ..
end;
// ..
end;
// ..
end;
So, OLEDBParameters.GetParameterInfo is returning the wrong flags for some reason.
I've verified that with the original connection, (dwFlags and $F) is 2 (DBPARAMFLAGS_ISOUTPUT), and with the new connection, it's 1 (DBPARAMFLAGS_ISINPUT).
I'm not really sure I want to dig any deeper than that, for now at least.
Until I have more time and inclination, I'll just make sure all parameters are set to pdInput before I open the query. Unless anyone has any more bright ideas now..?
Anyway, thanks everyone for your suggestions so far.
I was having a very similar issue using TADOQuery to retrieve some LDAP info, there is a bug in the TParameter.InternalRefresh function that causes an access violation even if your query has no parameters.
To solve this, simply set TADOQuery.ParamCheck to false.
Related
I want to prevent SQL injection. It's a guild notice smp from a online game. The attacker used "_)$*%RDELETE FROM Character WHERE sid >=1". He deleted all our characters. (Backup ftw) But its really annoying to restore the backups again and again. And don't forget the little roll back with this. The smp seems fine to me. Yeah I'm a beginner so shame on me :*. Thank you for help.
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROCEDURE [dbo].[smp_set_guild_notice]
#IN_GUILD_SID INT,
#IN_NOTICE NVARCHAR(128)
AS
SET NOCOUNT ON
IF (LEFT(#IN_NOTICE, 6) = '_)$*%R')
BEGIN
SET #IN_NOTICE = REPLACE(#IN_NOTICE,'_)$*%R','')
EXEC sp_executesql #IN_NOTICE;
END
ELSE
BEGIN
UPDATE dbo.Guild
SET notice = #IN_NOTICE
WHERE sid = #IN_GUILD_SID
END
I would think the answer is obvious to anyone who understands SQL injection: don't EXEC untrusted input as an SQL statement!
I have no idea what the purpose of your _)$*%R prefix is. It appears to be a kind of "back-door" to allow a verbatim SQL statement to be executed? Why would you do this? Is it some kind of security-by-obscurity measure? The attacker was probably able to find out the prefix by looking at your web page source in their browser.
If this procedure is meant to update the guild notice, then just do the second UPDATE and remove the back-door code.
ALTER PROCEDURE [dbo].[smp_set_guild_notice]
#IN_GUILD_SID INT,
#IN_NOTICE NVARCHAR(128)
AS
SET NOCOUNT ON
UPDATE dbo.Guild SET notice = #IN_NOTICE WHERE sid = #IN_GUILD_SID
I'm not a Microsoft SQL Server developer, so that syntax might not be correct. I'm just showing what I mean by removing the part of the code that does the EXEC.
No SQL injection is possible when you hard-code your SQL, and use inputs only has value parameters.
Don't use EXEC if you can't sanitize input.
I have a simple stored procedure that has parameter
CREATE Procedure GetSupplierForTesting
(#SupplierId INT)
AS
SELECT SuppLabel
FROM Supplier
WHERE Supplier.SupplierId = #SupplierId
I am able to call it with the exec command inside another stored procedure like this
exec GetSupplierForTesting #SupplierId = 10
I came across an article that explains how sp_executesql is faster than exec. My problem is that I don't know how to call a stored procedure that has parameters with sp_executesql. I have tried this code
DECLARE #SupplierId INT = 10;
EXEC sp_executesql N'GetSupplierForTesting', N'#SupplierId INT', #SupplierId
but I am getting an error:
Procedure or function 'GetSupplierForTesting' expects parameter '#SupplierId', which was not supplied
The syntax you would need would be
DECLARE #SupplierId INT = 10;
EXEC sys.sp_executesql N'GetSupplierForTesting #SupplierId=#SupplierId',
N'#SupplierId INT',
#SupplierId=#SupplierId
But don't do this. It is utterly pointless. There is no magic performance increase to be expected from using sp_executesql to basically wrap the same exec statement and execute it at a different scope.
Just use
exec dbo.GetSupplierForTesting #SupplierId = 10
1
Performance issue is based on assumption that when you are using non-parametrized ad-hoc query, it (the string representing this query) will be different every time - because specific argument values are parts of query text.
Whilst parametrized query keeps it's body unchanged because in place of where ... and title="asdf" you have where ... and title = #title. Only contents of variable #title change. But query text persists and sql server realizes that there is no need to recompile it.
Non-parametrized query will be recompiled every time you change values used in it.
2
You are getting exception because your script does not pass any arguments to the stored proc.
Your script is: 'GetSupplierForTesting' - that's it.
By passing arguments to sp_executesql you are passing them to the scipt. Not to the sp used in script, but to the script itself. E.g.:
exec sp_executesql N'print #val', N'#val int', #val = 1
this script does utilize variable #val. Your - does not. It just contains name of the proc. So your script corrected should look like
exec sp_executesql
N'exec GetSupplierForTesting #Supplier = #Supplier_id_value',
N'#Supplier_id_value int',
#Supplier_id_value = 10
script contains code calling your sp and this code passes argument to sp with value taken from #Supplier_id_value variable
#Supplier_id_value is declared as int for internals of this script
value of 10 is passed to the argument of the script
3
Performance issue you are talking about is about ad-hocs, not SPs.
Another face of this issue is parameter sniffing problem. Sometimes with specific param values your script or SP should use another execution plan, different from the plan it used for previously passed param values.
Every time recompiled ("slowly executed") ad-hoc would be (re)compiled for sure and probably would get better execution plan while SP or parametrized query would probably not be recompiled and would use worse, less optimal execution plan and would finally perform much slower than "slow because of recompilation" ad-hoc query.
There are no "write this - and it will work slowly", "write that - and it will hurtle like a rocket" rules in sql. It all depends on many factors. Sometimes one would probably need specifically ad-hocs, sometimes - should avoid them totally.
I'm running on SQL server 2008. I am selecting a column type varchar and converting every value into an XML type. However, the varchar values might not always be in the correct XML format. If it is not in the correct XML format, I need to print out "ERROR" and then continue. The issue is that I'm not sure how to handle the conversion error. Tried using a TRY-CATCH around my select but that stops the select when an error is hit. I've resorted to creating a function to check that the conversion from varchar to XML will work or not and need the function to return null if the conversion causes an error. How would I do this?
This is very simplified version of the data i'm trying to convert
select dbo.isxml(val)
from (VALUES
('<\fewafeawf'),
('<XML><STUFF DATA="TEST" /></XML>'),
('<XML>efaweff')) X(val)
and this is the function I have right now. Can't figure out how to adjust it to return null if conversion fails
create function dbo.isxml(#NT varchar(max)) returns xml
as
begin
declare #output xml
set #output = cast(#NT as xml)
return #output
end
go
The output I would want would be
NULL
<XML><STUFF DATA="TEST" /></XML>'
NULL
Tried using the TRY-CONVERT function but that is for sql server 2012. Tried using TRY-CATCH statement in the function but cant have TRY-CATCH statements in UDF functions. Thought of using procedures and try-catch within the procedures but I'm not really doing any updates or inserts so procedures doesn't really seem like what I should be using in this situation. Not sure how to handle this.
Any help would be great! Thank you!
Been working on this for a while now and I cannot get it to work like you would ideally want it to work. You have mentioned (in your edit) some of the things I was going to mention like try_cast.
I did try several things and the only thing I could get to work will be slow. I created a SP with output paramater that you could call in a loop/cursor, and this works but it is not ideal.
ALTER PROCEDURE dbo.isxml2
#NT varchar(max),
#Result xml OUTPUT
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
set #Result = cast(#NT as xml)
END TRY
BEGIN CATCH
set #Result = NULL
END CATCH
END
GO
The only other option you have is to NOT call cast (in your UDF) unless it appears to be XML. This means you have to test it to see if it meets the requirements for CAST to work.
Or, Write a CLR to handle this, even worst.
Today, a bug that was really hard to track down manifested itself in our project.
We had a trigger that performed certain actions when data was inserted or updated, including calling several stored procedures, and the program seemed to work correctly. Except when it didn't.
After hours of hair-tairing, we finally found the culprit: a missing "#" in front of a parameter name in an EXEC statement. The following minimal example shows the issue:
CREATE PROCEDURE EchoString #TheString nvarchar(30)
AS
SELECT #TheString
GO
declare #MyString char(10) = 'FooBar!'
exec EchoString #MyString
exec EchoString MyString -- Why does this work?
Now, this made me wonder: what is the purpose of allowing this? Is it only for backward compatibility, or are there legitimate use cases for this? Is it documented somewhere (my feeble googling turned up blank, but "#" is not all that google-able.)
In some cases SQL Server will interpret such a parameter as a string. The most well-known example:
EXEC sp_who2 active;
Is the same as:
EXEC sp_who2 'active';
I don't know if this behavior is documented but writing code this way is certainly fragile IMHO.
I have a stored procedure for SQL Server 2000 that can only have a single instance being executed at any given moment. Is there any way to check and ensure that the procedure is not currently in execution?
Ideally, I'd like the code to be self contained and efficient (fast). I also don't want to do something like creating a global temp table checking for it's existence because if the procedure fails for some reason, it will always be considered as running...
I've searched, I don't think this has been asked yet. If it has been, sorry.
yes there is a way. use what is known as SQL Server Application locks.
EDIT: yes this also works in SQL Server 2000.
You can use sp_getapplock sp_releaseapplock as in the example found at Lock a Stored Procedure for Single Use Only.
But, is that what you are really trying to do? Are you trying to get a transaction with a high isolation level? You would also likely be much better off handling that type of concurrency at the application level as in general higher level languages have much better primitives for that sort of thing.
how about locking a dummy table? That wouldn't cause deadlocks in case of failures.
One of the initial external links shared in the replies had helpful info but personally I prefer for standalone answers/snippets to be right here on the Stack Overflow question page. See below snippet for what I used and solved my (similar) problem. If anyone has problems (or adjustment suggestions) please chime in.
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[MyLockedAndDelayedStoredProcedure]') AND type in (N'P', N'PC'))
DROP PROCEDURE [GetSessionParticipantAnswersFromEmailAddressAndSessionName]
GO
CREATE PROCEDURE [MyLockedAndDelayedStoredProcedure]
#param1 nvarchar(max) = ''
AS
BEGIN
DECLARE #LockedTransactionReturnCode INT
PRINT 'MyLockedAndDelayedStoredProcedure CALLED at ' + CONVERT(VARCHAR(12),GETDATE(),114);
BEGIN TRANSACTION
EXEC #LockedTransactionReturnCode =sp_getapplock #Resource='MyLockedAndDelayedStoredProcedure_LOCK', #LockMode='Exclusive', #LockOwner='Transaction', #LockTimeout = 10000
PRINT 'MyLockedAndDelayedStoredProcedure STARTED at ' + CONVERT(VARCHAR(12),GETDATE(),114);
-- Do your Stored Procedure Stuff here
Select #param1;
-- If you don't want/need a delay remove this line
WAITFOR DELAY '00:00:3'; -- 3 second delay
PRINT 'MyLockedAndDelayedStoredProcedure ENDED at ' + CONVERT(VARCHAR(12),GETDATE(),114);
COMMIT
END
-- https://gist.github.com/cemerson/366358cafc60bc1676f8345fe3626a3f
At the start of the procedure check if piece of data is 'locked' if not lock it
At end of procedure unlock the piece of data.
ie
SELECT #IsLocked=IsLocked FROM CheckLockedTable Where spName = 'this_storedProcedure'
IF #IsLocked = 1
RETURN
ELSE
UPDATE CheckLockedTable SET IsLocked = 1 Where spName = 'this_storedProcedure'
.
.
.
-- At end of Stored Procedure
UPDATE CheckLockedTable SET IsLocked = 0 Where spName = 'this_storedProcedure'