SQL Server: как разбирать код на разные операторы - PullRequest
0 голосов
/ 02 июля 2019

Как возможное решение для SQL Server: Как найти строки, которые выполняются Я предложил добавить оператор вставки после каждого оператора.

Каким был бы эффективный способ разбить код хранимой процедуры на различные операторы, чтобы после каждого оператора можно было добавить дополнительный оператор вставки с предыдущей строкой? Если одна и та же строка встречается более одного раза внутри процедуры / функции / триггера, их также необходимо сделать уникальными с некоторым числом.

Комментарии и стиль не должны приниматься во внимание. Но важно, чтобы точный поток выполнения можно было отслеживать

Пример ввода 1:

/*******************************************************************************************
    description
    @param wioho
*******************************************************************************************/
CREATE PROC usp_Example1
    (
        @param VARCHAR(MAX),
        @param2 INT
    )
AS
BEGIN
    BEGIN TRY
        -- random comment
        INSERT INTO dept VALUES (@param, @param2)
        IF EXISTS (
                SELECT 1
                    FROM dept 
                    WHERE deptno = 10
            )
            THROW 50001, 'Dept 10 exists', 1
        ELSE
            INSERT INTO dept VALUES (@param, @param2)
    END TRY
    BEGIN CATCH
        THROW
    END CATCH
END

Ожидаемый результат 1 (или функционально-эквивалентный):

/*******************************************************************************************
    description
    @param wioho
*******************************************************************************************/
CREATE PROC usp_Example1
    (
        @param VARCHAR(MAX),
        @param2 INT
    )
AS
BEGIN
    BEGIN TRY
        INSERT INTO coverageTrace VALUES ('usp_Example1', 'BEGIN TRY', 1)
        -- random comment
        INSERT INTO dept VALUES (@param, @param2)
        INSERT INTO coverageTrace VALUES ('usp_Example1', 'INSERT INTO dept VALUES (@param, @param2)', 1)
        IF EXISTS (
                SELECT 1
                    FROM dept 
                    WHERE deptno = 10
            )
            BEGIN
                INSERT INTO coverageTrace VALUES ('usp_Example1', 'IF EXISTS (SELECT 1 FROM dept WHERE deptno = 10)', 1)
                THROW 50001, 'Dept 10 exists', 1
            END
        ELSE IF 1 = 1
        BEGIN
            INSERT INTO dept VALUES (@param, @param2)
            INSERT INTO coverageTrace VALUES ('usp_Example1', 'INSERT INTO dept VALUES (@param, @param2)', 2)
        END
    END TRY
    BEGIN CATCH
        INSERT INTO coverageTrace VALUES ('usp_Example1', 'BEGIN CATCH', 1)
        THROW
    END CATCH
END

Теперь, если кто-то неправильно оформляет свой код, это все равно должно работать. Пример ввода 2:

/*******************************************************************************************
    description @param wioho
*******************************************************************************************/
CREATE PROC usp_Example1(@param VARCHAR(MAX),@param2 INT) AS BEGIN
    BEGIN TRY-- random comment
INSERT INTO dept VALUES (@param, @param2) IF EXISTS (
                SELECT 1
                    FROM dept 
                    WHERE deptno = 10
            )
            THROW 50001, 'Dept 10 exists', 1 ELSE
            INSERT INTO dept VALUES (@param, @param2) END TRY BEGIN CATCH
        THROW
    END CATCH
END

Это должно дать (функционально) эквивалентный код ожидаемому результату 1

Обратите внимание, что этот код в случае операторов блока должен быть в состоянии знать, НАЧИНАЕТСЯ ЛИ БЫСТРЫЙ или КОНЕЦ, где он используется явно. Таким образом, код может добавить его явно, если это необходимо.

Имеется ли какой-либо код, который можно использовать повторно, или, возможно, я могу использовать регулярные выражения. Если возможно, я бы хотел сделать это в SQL, чтобы моя среда тестирования мутаций могла быть одним файлом на любом MS SQL Server.

Обратите внимание, что : это среда тестирования, и ручное изменение кода не опция, это имеет , который должен быть выполнен автоматически.

Обновление прогресса: После комментария @Jeroen Mostert я начал экспериментировать с расширенной системой событий. У меня все еще есть пара проблем, как правильно фильтровать сгенерированный XML и как выполнять трассировку только в базе данных без жесткого кодирования в имени базы данных? (Исправлено генерацией кода (не было выпущено) Мне нужно было использовать широкий набор символов внутри поколения))

Текущий код:

    USE master
    GO

    DROP DATABASE IF EXISTS testMSSQLDB
    GO

    CREATE DATABASE testMSSQLDB
    GO

    USE testMSSQLDB
    GO

    CREATE TYPE ID FROM INT
    GO

    CREATE TABLE dept (
        deptno ID PRIMARY KEY
    )
    GO

    IF EXISTS(SELECT * FROM sys.server_event_sessions WHERE name='testMSSQLTrace')  
       DROP EVENT SESSION testMSSQLTrace ON SERVER;  

    DECLARE @cmd VARCHAR(MAX) = '';
SELECT @cmd = 'CREATE EVENT SESSION testMSSQLTrace 
ON SERVER
    ADD EVENT sqlserver.module_end
    (SET collect_statement = (1)
        WHERE (sqlserver.database_name = N''' + DB_NAME() + ''')),
    --ADD EVENT sqlserver.rpc_completed
    --(WHERE (sqlserver.database_name = N''' + DB_NAME() + ''')),
    ADD EVENT sqlserver.sp_statement_completed
        (WHERE (sqlserver.database_name = N''' + DB_NAME() + ''')),
    --ADD EVENT sqlserver.sql_batch_completed
    --(WHERE (sqlserver.database_name = N''' + DB_NAME() + ''')),
    ADD EVENT sqlserver.sql_statement_completed
        (WHERE (sqlserver.database_name = N''' + DB_NAME() + '''))
    ADD TARGET package0.ring_buffer
        WITH (
            MAX_MEMORY = 2048 KB,
            -- EVENT_RETENTION_MODE = NO_EVENT_LOSS,
            MAX_DISPATCH_LATENCY = 3 SECONDS,
            MAX_EVENT_SIZE = 0 KB,
            MEMORY_PARTITION_MODE = NONE,
            TRACK_CAUSALITY = OFF,
            STARTUP_STATE = OFF
        );'

EXEC (@cmd)

    ALTER EVENT SESSION testMSSQLTrace
          ON SERVER
        STATE = STOP; 

    ALTER EVENT SESSION testMSSQLTrace
          ON SERVER
        STATE = START;  

    GO

    CREATE OR ALTER PROC usp_temp
        (
            @param INT = 10 
        )
    AS
    BEGIN
        IF @param = 10
        BEGIN
            DELETE dept
            INSERT INTO dept VALUES (@param)
            SELECT * FROM dept
        END
        ELSE
            DELETE dept
    END
    GO

    EXEC usp_temp
    EXEC usp_temp 20

    SELECT name, target_name, CAST(xet.target_data AS xml)
    FROM sys.dm_xe_session_targets AS xet  
    JOIN sys.dm_xe_sessions AS xe  
       ON (xe.address = xet.event_session_address)  
    WHERE xe.name = 'testMSSQLTrace'

Создает (вырезает некоторые части):

<RingBufferTarget truncated="0" processingTime="0" totalEventsProcessed="12" eventCount="12" droppedCount="0" memoryUsed="2012">
<event name="sp_statement_completed" package="sqlserver" timestamp="2019-07-04T09:22:30.472Z">
    <data name="source_database_id">
      <type name="uint32" package="package0" />
      <value>22</value>
    </data>
    <data name="object_id">
      <type name="int32" package="package0" />
      <value>1916742081</value>
    </data>
    <data name="object_type">
      <type name="object_type" package="sqlserver" />
      <value>8272</value>
      <text>PROC</text>
    </data>
    <data name="duration">
      <type name="int64" package="package0" />
      <value>22</value>
    </data>
    <data name="cpu_time">
      <type name="uint64" package="package0" />
      <value>0</value>
    </data>
    <data name="physical_reads">
      <type name="uint64" package="package0" />
      <value>0</value>
    </data>
    <data name="logical_reads">
      <type name="uint64" package="package0" />
      <value>3</value>
    </data>
    <data name="writes">
      <type name="uint64" package="package0" />
      <value>0</value>
    </data>
    <data name="row_count">
      <type name="uint64" package="package0" />
      <value>1</value>
    </data>
    <data name="last_row_count">
      <type name="uint64" package="package0" />
      <value>1</value>
    </data>
    <data name="nest_level">
      <type name="uint16" package="package0" />
      <value>1</value>
    </data>
    <data name="line_number">
      <type name="int32" package="package0" />
      <value>11</value>
    </data>
    <data name="offset">
      <type name="int32" package="package0" />
      <value>214</value>
    </data>
    <data name="offset_end">
      <type name="int32" package="package0" />
      <value>276</value>
    </data>
    <data name="object_name">
      <type name="unicode_string" package="package0" />
      <value />
    </data>
    <data name="statement">
      <type name="unicode_string" package="package0" />
      <value>INSERT INTO dept VALUES (@param)</value>
    </data>
  </event>
  <event name="sp_statement_completed" package="sqlserver" timestamp="2019-07-04T09:22:30.476Z">
    <data name="source_database_id">
      <type name="uint32" package="package0" />
      <value>22</value>
    </data>
    <data name="object_id">
      <type name="int32" package="package0" />
      <value>1916742081</value>
    </data>
    <data name="object_type">
      <type name="object_type" package="sqlserver" />
      <value>8272</value>
      <text>PROC</text>
    </data>
    <data name="duration">
      <type name="int64" package="package0" />
      <value>32</value>
    </data>
    <data name="cpu_time">
      <type name="uint64" package="package0" />
      <value>0</value>
    </data>
    <data name="physical_reads">
      <type name="uint64" package="package0" />
      <value>0</value>
    </data>
    <data name="logical_reads">
      <type name="uint64" package="package0" />
      <value>2</value>
    </data>
    <data name="writes">
      <type name="uint64" package="package0" />
      <value>0</value>
    </data>
    <data name="row_count">
      <type name="uint64" package="package0" />
      <value>1</value>
    </data>
    <data name="last_row_count">
      <type name="uint64" package="package0" />
      <value>1</value>
    </data>
    <data name="nest_level">
      <type name="uint16" package="package0" />
      <value>1</value>
    </data>
    <data name="line_number">
      <type name="int32" package="package0" />
      <value>12</value>
    </data>
    <data name="offset">
      <type name="int32" package="package0" />
      <value>286</value>
    </data>
    <data name="offset_end">
      <type name="int32" package="package0" />
      <value>320</value>
    </data>
    <data name="object_name">
      <type name="unicode_string" package="package0" />
      <value />
    </data>
    <data name="statement">
      <type name="unicode_string" package="package0" />
      <value>SELECT * FROM dept</value>
    </data>
  </event>
</RingBufferTarget>

Как я могу отфильтровать этот XML таким образом, чтобы остались только выполненная инструкция, тип объекта и идентификатор объекта, из которого он был выполнен? Конкретная потребность в информации заключается в том, что мне нужно знать, какие строки хранимой процедуры были выполнены, хранимая процедура может вызывать другую хранимую процедуру, в этом случае мне все еще нужно знать, какие операторы выполняла процедура и была ли она вложена в первую сохраненную процедуру. процедура. И если один и тот же оператор встречается несколько раз, мне нужно, чтобы его (относительный) номер строки

Или в предикатах: Процедура X в хранимой процедуре верхнего уровня Y выполненная строка Z с номером белья J

Процедура X в хранимой процедуре верхнего уровня Y выполненная строка W с номером белья I

РЕДАКТИРОВАТЬ: Я провел еще несколько исследований и пришел к выводу, что мне нужны все события, которые имеют поле <data name="nest_level"><value>2</value></data>. Где 2 - любое значение больше 1.

Эта ссылка https://www.scarydba.com/2018/09/24/extended-events-and-stored-procedure-parameter-values/ оказалась полезной для меня, чтобы получить все данные.

1 Ответ

0 голосов
/ 09 июля 2019

Итак, расширенные события - это решение, вот как я это сделал:

IF EXISTS(SELECT * FROM sys.server_event_sessions WHERE name='testMSSQLTrace')  
   DROP EVENT SESSION testMSSQLTrace ON SERVER;  

DECLARE @cmd VARCHAR(MAX) = '';
SELECT @cmd = 'CREATE EVENT SESSION testMSSQLTrace 
ON SERVER
    ADD EVENT sqlserver.sp_statement_completed
        (WHERE (sqlserver.database_name = N''' + DB_NAME() + '''))
    ADD TARGET package0.ring_buffer
        WITH (
            MAX_MEMORY = 2048 KB,
            EVENT_RETENTION_MODE = NO_EVENT_LOSS,
            MAX_DISPATCH_LATENCY = 3 SECONDS,
            MAX_EVENT_SIZE = 0 KB,
            MEMORY_PARTITION_MODE = NONE,
            TRACK_CAUSALITY = OFF,
            STARTUP_STATE = OFF
        );'

EXEC (@cmd)

Это создает событие, которое может быть запущено после каждого завершения оператора, это выполняется динамически для фильтрации в базе данных

Тогда у меня есть 3 процедуры, которые облегчают управление этим событием

/*******************************************************************************************
    Starts the statement trace
*******************************************************************************************/
CREATE OR ALTER PROC testMSSQL.Private_StartTrace
AS
BEGIN 
    ALTER EVENT SESSION testMSSQLTrace
          ON SERVER
        STATE = START; 
END
GO

/*******************************************************************************************
    Ends the statement trace, this also clears the trace
*******************************************************************************************/
CREATE OR ALTER PROC testMSSQL.Private_StopTrace
AS
BEGIN
    ALTER EVENT SESSION testMSSQLTrace
          ON SERVER
        STATE = STOP; 
END
GO


/*******************************************************************************************
    Saves the statements trace
*******************************************************************************************/
CREATE OR ALTER PROC testMSSQL.Private_SaveTrace
AS
BEGIN
    DECLARE @xml XML;

    SELECT @xml = CAST(xet.target_data AS xml)
        FROM sys.dm_xe_session_targets AS xet INNER JOIN sys.dm_xe_sessions AS xe ON (xe.address = xet.event_session_address)  
        WHERE xe.name = 'testMSSQLTrace'  

    INSERT INTO testMSSQL.StatementInvocations (testProcedure, procedureName, lineNumber, statement)
        SELECT testMSSQL.GetCurrentTest(), 
            OBJECT_NAME(T.c.value('(data[@name="object_id"]/value)[1]', 'int')),
            T.c.value('(data[@name="line_number"]/value)[1]', 'int'), 
            T.c.value('(data[@name="statement"]/value)[1]', 'VARCHAR(900)')
        FROM @xml.nodes('RingBufferTarget/event') T(c)
        WHERE T.c.value('(data[@name="nest_level"]/value)[1]', 'int') > 3

END
GO

Эти процедуры соответственно запускают и останавливают трассировку, а последняя сохраняет результат в таблице, где она фильтруется на уровне гнезда, поэтому мой собственный код не отслеживается.

Наконец, я использую это немного так:

start trace
start tran/savepoint
run SetUp (users code)
run test (users code)
save trace
save trace to variable
rollback tran (also catch errors and stuff like that)
save variable back to table so the trace is not rolled back
...