I'm trying to debug a rather complicated formula evaluator written in T-SQL UDFs (don't ask) that recursively (but indirectly through an intermediate function) calls itself, blah, blah.
And, of course, we have a bug.
Now, using PRINT statements (that can then be read from ADO.NET by implementing a handler for the InfoMessage event), I can simulate a trace for stored procedures.
Doing the same for UDF results in a compile time message:
Invalid use of side-effecting or time-dependent operator in 'PRINT' within a function.
I get the message (PRINT does some stuff like resetting ##ROWCOUNT which definitly is a no-no in UDFs, but how can I trace through the calls? I want to have this trace printed out, so I can study it without getting distracted by stepping through the calls in the debugger...
EDIT: I have tried to use the SQL Profiler (this was a first time one for me), but I can't figure out what to trace for: Although I can get the trace to output the queries sent to the database, they are opaque in the sense that I can't drill down to the Expression-UDFs called: I can trace the actual Stored Procedure invoked, but the UDFs called by this procedure are not listed. Am I missing something? I guess not...
EDIT #2: Allthough the (auto-)accepted answer does trace the function calls - very helpful, thanks - it does not help in finding out what parameters were passed to the function. This, of course, is essential in debugging recursive functions. I will post if I find any sollution at all...
Why not use SQL Profiler with statement level events added?
Edit: Add events for Stored Procedures : SP:Stmt Starting or SP:Stmt Completed
Use variables to debug if needed, i.e. set #debug='i am here'; UDF's, while not technically stored procedures, will get traced with the statement level events.
In the SQL profiler, you need: SP:Starting, SP:StmtStarting, SP:Completed, SQL:BatchStarting. Then, you get every entry, exit from the functions/stored procedures.
alter FUNCTION [dbo].[ufn_mjf](#i numeric(10))
RETURNS numeric(20)
AS
BEGIN
declare #datapoint varchar(10)
set #datapoint = 'hello world'
return #i
END
go
drop table foo
go
create table dbo.foo ( foo_id numeric(10))
go
delete from foo
insert into foo ( foo_id ) values ( 1 )
insert into foo ( foo_id ) values ( 2 )
select foo_id, dbo.ufn_mjf(foo_id) from foo
with this, I get:
SQL:BatchStarting alter FUNCTION [dbo].[ufn_mjf](#i numeric(10))
SQL:BatchStarting drop table foo
SQL:BatchStarting create table dbo.foo ( foo_id numeric(10))
SQL:BatchStarting delete from foo
insert into foo ( foo_id ) values ( 1 )
insert into foo ( foo_id ) values ( 2 )
select foo_id, dbo.ufn_mjf(foo_id) from foo
SP:Starting select foo_id, dbo.ufn_mjf(foo_id) from foo
SP:StmtStarting set #datapoint = 'hello world'
SP:StmtStarting return #i
SP:Completed select foo_id, dbo.ufn_mjf(foo_id) from foo
SP:Starting select foo_id, dbo.ufn_mjf(foo_id) from foo
SP:StmtStarting set #datapoint = 'hello world'
SP:StmtStarting return #i
SP:Completed select foo_id, dbo.ufn_mjf(foo_id) from foo
is that enough for you?
This looks like what you need but it's only available in team/pro versions of Visual Studio.
Use SQL Profiler, I recommend you go overboard on adding events the first time around which will let you get a feel for what you need. Without testing I would add the events for SP:StmtStarted (or Completed or both), SQL:StmtStarted (again Completed or Both).
I second the SQL Profiler suggestion. Take some time to set it up so that only the events you are interested in are logged to cut output size. You can output the trace to a file - I have frequently then loaded that file back into a table to enable analysis. (extremely handy for performance analysis, though no doubt someone will tell me that 2008 has this all built in somwehere...)
Sometimes you won't have permissions to run SQL Profiler as it does slow the server down - ask your DBA to grant you permission on your Dev server. They shouldn't have any problems with that.
Well in the past I have had to take typical values that would be in the UDF and then run just the udf part in a separate query window as straight SQL not a udf using the typical values as variables set with a declare and a set statement. If it is run from a table instead of having only one value, I would set up a temp table or table variable with the input values and then run them through the sql in the UDF (but again as straight SQL not a UDF) through a cursor. By running straight SQL you could have print statements in it to see what is happening. I know this is a pain, but it works. (I go through a simliar process when creating/debugging triggers, setup #inserted and #deleted with my test values and then test the code I intend to put into the trigger, then global replace the # with nothing and add the create trigger code.)
Maybe you can use SQL CLR to do the tracing as described here
How to log in T-SQL
Can you take your function, and make a second copy of it, but returning a table type with an additional column for your debug information.
For example, the mySum function below
CREATE FUNCTION mySum
(
#param1 int,
#param2 int
)
RETURNS INT AS
BEGIN
DECLARE #mySum int
SET #mySum = #param1
SET #mySum = #mySum + #param2
RETURN #mySum
END
GO
SELECT dbo.mySum(1, 2)
Would turn into
CREATE FUNCTION mySumDebug
(
#param1 int,
#param2 int
)
RETURNS #myTable TABLE
(
[mySum] int,
[debug] nvarchar(max)
)
AS
BEGIN
DECLARE #debug nvarchar(max)
SET #debug = 'Declare #mySum variable. '
DECLARE #mySum int
SET #debug = #debug + 'Set #mySum = #param1(' + CONVERT(nvarchar(50), #param1) + ') '
SET #mySum = #param1
SET #debug = #debug + 'Add #param2(' + CONVERT(nvarchar(50), #param2) + ') to #mySum(' + CONVERT(nvarchar(50), #mySum) + ') '
SET #mySum = #mySum + #param2
SET #debug = #debug + 'Return #mySum variable. '
INSERT #myTable (mySum, debug) VALUES (#mySum, #debug)
RETURN
END
GO
SELECT mySum, debug FROM dbo.mySumDebug(1, 2)
Not an ideal solution, but useful just to return some text to help track down a bug.
I use SQL SPY which does what you are looking for and more.
SQL SPY
SQL SPY Feature Documentation
SQL SPY's Incoming SQL Sniffer shows the incoming SQL code of each connection (Includes DDL and DML statement tracking)
This feature is designed for MS SQL Server 2005\2008, but will work with MS SQL Server 2000 in limited scope. It has the ability to record and report on Incoming SQL. How to use the features: See
Disclosure: I am part of the SQL SPY team.
Related
I am trying to encapsulate the functionality from this sample code here, inside a Table-Function.
I can run the sample alone without any problem.
But when I create a table function, just with a single call to OPEN_CURSOR , I receive SQL0577N
CREATE FUNCTION ROW_CHECKSUM
( IN sSchema VARCHAR(128) ,
IN sTable VARCHAR(128) ,
IN sColumnList VARCHAR(1024) ,
IN sWhere VARCHAR(1023),
IN iRows INTEGER
)
RETURNS TABLE (ROW_PK_VALUES VARCHAR(3000), CHECKSUM INTEGER )
LANGUAGE SQL
SPECIFIC ROW_CHECKSUM
--NO EXTERNAL ACTION
--MODIFIES SQL DATA
--NOT DETERMINISTIC
BEGIN
DECLARE iCheckSum INTEGER ;
DECLARE sKyes VARCHAR(1024) ;
DECLARE iCursor INTEGER;
DECLARE sQuery VARCHAR(32000) ;
SET sQuery = 'SELECT ' || sColumnList || ' FROM "' || sSchema || '"."' || sTable || '" WHERE ' || sWhere || ' FETCH FIRST ' || TO_CHAR(iRows) || ' ONLY' ;
CALL DBMS_SQL.OPEN_CURSOR(iCursor);
--CALL DBMS_SQL.PARSE(iCursor, sQuery, DBMS_SQL.native) ;
--PIPE (sKeys, iCheckSum) ;
--PIPE ('abcd', 1234) ;
RETURN ;
END
----
SQL0577N User defined routine "DB2ADMIN.ROW_CHECKSUM" (specific name "")
attempted to modify data but was not defined as MODIFIES SQL DATA. LINE
NUMBER=33. SQLSTATE=38002
it seems, OPEN_CURSOR demands to have the MODIFY SQL DATA specified.. ok.. let's go!
But, when I specify it, then I get the following error, instead:
SQL0628N Multiple or conflicting keywords involving the "MODIFIES SQL DATA"
clause are present. LINE NUMBER=33. SQLSTATE=42613
The error details for -628 error is too generic and does not help me to determine what's really going on here.
I need to perform dynamic SQL queries using DBMS_SQL module, and return the result set using PIPE , like this other sample here.
I have been reading spread documentations the entire day.. and so far was not able to determine exactly what rule I am violating.
Also, found some inconsistencies on documentation, which I don't understand:
This page, says:
SQL table functions cannot contain compiled compound statements.
While, the Rules from RETURN statement says the opposite, and matches with PIPE sample code:
In an SQL table function using a compound SQL (compiled) statement, an expression, NULL, or fullselectcannot be specified. Rows are returned from the function using the PIPE statement and the RETURN statement is required as the last statement to execute when the function exits (SQLSTATE 2F005).
Appreciate any help!
Look at the note about the MODIFIES SQL DATA in the CREATE FUNCTION statement description:
4 Valid only for compiled scalar function definition and an inlined
table function definition.
But you can't use PIPE in an inlined function.
So, you want to use different functionalities, which can't be used together.
The inconsistency you found in the documentation is not related to you problem.
I just noticed that I could alter my stored procedure code with a misspelled user defined function in it.
I noticed that at 1st time I execute the SP.
Is there any way to get a compile error when an SP include an invalid user-defined function name in it?
At compile time? No.
You can, however, use some of SQL's dependency objects (if using MS SQL) to find problems just after deployment, or as part of your beta testing. Aaron Bertran has a pretty nice article rounding up the options, depending upon the version of SQL Server.
Here is an example using SQL Server 2008 sys object called sql_expression_dependencies
CREATE FUNCTION dbo.scalarTest
(
#input1 INT,
#input2 INT
)
RETURNS INT
AS
BEGIN
-- Declare the return variable here
DECLARE #ResultVar int
-- Add the T-SQL statements to compute the return value here
SELECT #ResultVar = #input1 * #input2
-- Return the result of the function
RETURN #ResultVar
END
GO
--Fn Works!
SELECT dbo.ScalarTest(2,2)
GO
CREATE PROCEDURE dbo.procTest
AS
BEGIN
SELECT TOP 1 dbo.scalarTest(3, 3) as procResult
FROM sys.objects
END
GO
--Sproc Works!
EXEC dbo.procTest
GO
--Remove a dependency needed by our sproc
DROP FUNCTION dbo.scalarTest
GO
--Does anything have a broken dependency? YES
SELECT OBJECT_NAME(referencing_id) AS referencing_entity_name,
referenced_entity_name, *
FROM sys.sql_expression_dependencies
WHERE referenced_id IS NULL --dependency is missing
GO
--Does it work? No
EXEC dbo.procTest
GO
I have jsut started to use tSQLt and is about to test a trigger. I call the FakeTable procedure and do my test but the trigger is not executed. If don't use FakeTable the trigger is executed. That seems to be really bad and I canät find any info that there is any method to readded them.
Then I thought the triggers are removed by FakeTable but I can recreate them after the call and did the following code in my test:
DECLARE #createTrigger NVARCHAR(MAX);
SELECT #createTrigger = OBJECT_DEFINITION(OBJECT_ID('MoveDataFromAToB'))
EXEC tSQLt.FakeTable 'dbo.A';
EXEC(#createTrigger);
I got the following error: "There is already an object named 'MoveDataFromAToB' in the database.{MoveDataFromAToB,14} (There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.{Private_RunTest,60})"
Anyone that have any experience with tSQLt and know anyworkaround for this problem?
There is an ApplyTrigger method on the tSQLt backlog, but it is not finished yet. For now you should be able to use this code in your test:
DECLARE #createTrigger NVARCHAR(MAX);
SELECT #createTrigger = OBJECT_DEFINITION(OBJECT_ID('MoveDataFromAToB'));
DROP TRIGGER MoveDataFromAToB;
EXEC tSQLt.FakeTable 'dbo.A';
EXEC(#createTrigger);
You need to drop the existing trigger as FakeTable does not drop the original table. It just renames it, which leaves the old trigger intact; hence the name collision you did see.
The rollback that tSQLt executes at the end of every test will put the dropped trigger back in place (unless you are doing something really bad in your code). If you are worried about that use sp_rename instead of drop on the trigger.
I would put all this into a helper stored procedure within the test class and call it from the tests that need it. That way, once we have a better solution implemented in tSQLt you will have to change only one place in your code.
Thanks Sebastian for your answer. It really helped me too :)
I made a stored proc for the code you gave. I will use this until the 'ApplyTrigger' function is available:
CREATE PROCEDURE [tSQLt].[FakeTableWithTrigger]
#TableName NVARCHAR(MAX),
#TriggerName NVARCHAR(MAX),
#SchemaName NVARCHAR(MAX) = NULL, --parameter preserved for backward compatibility. Do not use. Will be removed soon.
#Identity BIT = NULL,
#ComputedColumns BIT = NULL,
#Defaults BIT = NULL
AS
BEGIN
DECLARE #createTrigger NVARCHAR(MAX);
SELECT #createTrigger = OBJECT_DEFINITION(OBJECT_ID(#TriggerName));
EXEC('DROP TRIGGER ' + #TriggerName);
EXEC tSQLt.FakeTable #TableName, #SchemaName, #Identity, #ComputedColumns, #Defaults;
EXEC(#createTrigger);
END
An example of it's useage in a test is:
exec tSQLt.FakeTableWithTrigger 'dbo.MyTable', 'MyTable_SyncTrigger', #Identity = 1
Is there any way to reference the collection of parameters passed to a stored proc, without referencing each one by name?
The context here is error logging. I'd like to develop a generic CATCH clause, or sub-clause, that grabs all parameter values as well as other error and execution info and logs it to a table (one or more).
The basic version looks something like this:
CREATE PROC dbo.Proc1 #Param1 INT, #Param2 INT
AS
BEGIN TRY
SELECT 1/0
END TRY
BEGIN CATCH
DECLARE #params XML
SET #params = (
SELECT
#Param1 AS [Param1]
, #Param2 AS [Param2]
FOR XML PATH('params'), TYPE
)
EXEC dbo.LogError #procid = ##PROCID, #params = #params
EXEC dbo.RethrowError
END CATCH
Now, this template does work, except that for each individual procedure, I would have to edit the SET #params section manually.
I could script it out easily enough, but then I would still need to copy it in.
I could leave some sort of placeholder, and then dynamically update each definition w/ a correct SET statement. I could even write a database trigger that listens for CREATE and ALTER PROC statements and have it do this for me automagically.
But what I really want is just to grab the collection of parameters directly and be done with it. Is there any way?
EDIT: getting parameter metadata from the system tables is not enough. For example:
DECLARE #sql NVARCHAR(MAX)
SET #sql =
'SET #params = (SELECT '
+ STUFF((
SELECT ', '+name+' AS '+STUFF(name,1,1,'')
FROM sys.parameters WHERE object_id = ##PROCID
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)')
,1,2,'')
+ ' FOR XML PATH(''params''), TYPE)'
-- inline execute here....alas, there's isn't a mode for that
EXEC sp_executesql #sql, N'#params XML OUTPUT', #params OUTPUT
This, of course, generates an error, since I haven't declared parameters for the nested scope of sp_executesql. While I could script out the second param of sp_executesql, I can't assign them without explicitly assigning them one at a time by name.
Original question, rephrased: is there another flavor of sp_executesql that inherits variable "context" from the outer scope, that executes in the same frame, that inlines the command, rather than executing in a nested scope?
Why not use sys.dm_exec_sql_text(#sql_handle) to retrieve the SQL command, using sys.sysprocesses.sql_handle?
Is there a way to reuse the ?'s used on a DBI prepare statement. Consider the following code:
$sth=$dbh->prepare("INSERT INTO mytable(a,b,c) SELECT ?,B(?),C(?)");
$sth->execute($a,$a,$a);
It would be very nice to instead use something like this:
#I'm making this up as something I hope exists
$sth=$dbh->prepare("INSERT INTO mytable(a,b,c) SELECT ?,B(?:1),C(?:1)");
$sth->execute($a);
Notice that only one $a is passed to the execute instead of three. Is there a way to do this in real life?
It depends on your DBD. For example, using DBD::Pg with the $1 style of placeholders, or DBD::Oracle with named placeholders and bind_param, you can do exactly what you like. But using the general purpose ? style of placeholders that works DBI-wide, it's not possible.
If you use a library to generate your SQL statements for you, e.g. SQL::Abstract or a full-on ORM like DBIx::Class, you won't have to worry about things like that.
Alternatively you can do something similar with just a few lines of code:
my $sql = 'INSERT INTO ...blah blah... VALUES (' . (join(', ', ('?') x scalar(#insert_elements))) . ')';
#hobbs' answer is right -- default DBI placeholders can't do it. #Ether's answer is right -- a SQL abstraction can make this a non-issue.
However, typically one need only bind each distinct parameterized value once. In your example, using a scalar derived table makes the user-supplied value available by name to the rest of the query:
my $sth = $dbh->prepare(<<'__eosql');
INSERT INTO mytable(a,b,c)
SELECT x, B(x), C(x) FROM (SELECT ? AS x) subq
-- Subquery may vary from DB to DB:
-- "FROM (SELECT ? AS x FROM DUAL) subq"
-- "FROM (SELECT ? FROM rdb$database) subq(x)"
-- "FROM (VALUES (?)) subq(x)"
-- etc.
__eosql
for $v (#values) {
$sth->execute($v);
}
Usually this is incrementally more "wire efficient" than the alternative, since the user-supplied parameter is typically transmitted just once instead of N times.
You can set SQL variables in one SQL statement and then use that variable multiple times in the next query.
$dbh->do('set #reusable = ?', undef, $perl_var);
$dbh->select_arrayref('select * from table where cola = #reusable or colb = #reusable');
No duplicated variables and you still get the safety of parameterized queries.