Bezdepbonusu7Программирование: статьи и ответы

Вопрос

SQL CLR - ошибка разрешения на TSQL UDF, даже если у пользователя есть доступ

Автор вопроса: thomasОтветов: 1

Я использую контекстное соединение в скалярной функции SQL CLR. Функция создает динамический запрос, а затем выполняет его (получая данные/логику из разных мест для построения запроса).

Я могу "построить" динамическую строку вручную и запустить ее в стандартном окне Management Studio, и она работает нормально. Когда тот же запрос выполняется через CLR в контекстном соединении, я получаю сообщение об ошибке.

Я отладил эту вещь и обнаружил, что ошибка связана с проблемой разрешения на UDF TSQL, который вызывается в запросе. Созданное сообщение об ошибке:

Отказано в разрешении EXECUTE на be.udfDivide

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

Запрос эффективно:

select be.udfDivide(sum(Numerator), sum(Denominator))
from Table where <some stuff>

В <some stuff> живет вся сложная логика, но я уверяю вас, что это просто предложение where, и эта функция CLR успешно запускается миллионы раз в день, когда в запросе нет ссылки на UDF.

С помощью некоторого творческого взлома запросов я смог подтвердить, что контекстное соединение выполняется под правильным пользователем (как SYSTEM_USER, так и CURRENT_USER), и я знаю, что у этого пользователя есть разрешения на UDF. Таким образом, похоже, что причиной этого является нарушение цепочки владения. Только пока не знаю, как это исправить.

Я знаю, что могу удалить UDF в этом конкретном случае, так как это простое деление (хотя UDF обрабатывает деление на ноль для нас), но в случае более сложных UDF мы хотели бы иметь возможность ссылаться на них в запросе.


Функция be.udfDivide приведена ниже для справки:

ALTER function [be].[udfDivide](@Numerator float, @Demoninator float,
                                @ValueIfDivideByZero float)
returns float
as
begin
    declare @result float
    if @Demoninator = 0
        begin set @result = @ValueIfDivideByZero end
    else
        begin set @result = @Numerator / @Demoninator end
    return @result
end

sql-servertsqluser-defined-functionssql-server-2012sqlclr

Ответы

Ответ 1 · рейтинг 1 · Solomon Rutzky

То, что здесь происходит, не задокументировано нигде, что я мог найти, но, похоже, требуется указать SystemDataAccess = SystemDataAccessKind.Read в атрибуте SqlFunction (по умолчанию он установлен на None) при выполнении запросов, содержащих T-SQL Scalar UDF или Multi-statement TVF, НО только если функция не привязана к схеме. Это означает, что вам может сойти с рук SystemDataAccess = SystemDataAccessKind.None, если ссылочная функция:

  • встроенный TVF
  • скалярная UDF или TVF с несколькими операторами, созданная с использованием параметра WITH SCHEMABINDING

Имеет смысл, что у встроенных TVF нет проблем, поскольку их определение втягивается в запрос, в котором на них ссылаются, и они не вызываются как отдельные объекты.

Я предполагаю, что причина требования "системного" доступа к данным для не привязанных к схеме UDF и TVF с несколькими операторами заключается в том, что представления системного каталога, вероятно, необходимо проверять во время выполнения, чтобы убедиться, что все еще существуют какие-либо зависимости и разрешения. действительны, поскольку нельзя гарантировать, что они будут такими же, как они были изначально, из-за отсутствия привязки схемы (что предотвращает определенные изменения объектов, которые являются зависимостями других объектов).

Имея это в виду, самым быстрым/самым прямым исправлением (которое позволяет SystemDataAccess оставаться неопределенным или, по крайней мере, иметь значение None) будет добавление этой опции в UDF следующим образом:

ALTER FUNCTION [be].[udfDivide](@Numerator FLOAT, @Denominator FLOAT,
                                @ValueIfDivideByZero FLOAT)
RETURNS FLOAT
WITH SCHEMABINDING -- this option helps in a few ways
AS
BEGIN
    DECLARE @Result FLOAT;
    IF (@Denominator = 0)
    BEGIN
      SET @Result = @ValueIfDivideByZero;
    END;
    ELSE
    BEGIN
      SET @Result = (@Numerator / @Denominator);
    END;

    RETURN @Result;
END;

НО, если этот запрос выполняется так часто, вам, безусловно, следует преобразовать его во встроенный TVF. Существует огромный удар по производительности при использовании UDF, и ITVF обычно являются ответом на этот вопрос. Вам просто нужно выразить ту же логику в виде одного запроса (используйте CASE для "если-то-иначе"), а затем использовать его через CROSS APPLY, а не в списке SELECT, оба из которых достаточно просты.

Здесь та же логика, что и у Inline TVF:

CREATE FUNCTION [be].[itvfDivide](@Numerator FLOAT, @Denominator FLOAT,
                                @ValueIfDivideByZero FLOAT)
RETURNS TABLE
WITH SCHEMABINDING -- not necessary, but still a good idea to use
AS RETURN
   SELECT CASE @Denominator
             WHEN 0 THEN @ValueIfDivideByZero
             ELSE (@Numerator / @Denominator)
          END AS [Result];
GO

А вот и простой тест:

SELECT itvf.[Result]
FROM   (VALUES (1, 2, NULL), (3, 0, -5.789), (1.1, 5.67567, -999)
       ) tbl(num, den, dbz)
CROSS APPLY be.itvfDivide(tbl.[num], tbl.[den], tbl.[dbz]) itvf;

Этот ITVF будет намного быстрее, чем текущий Scalar UDF :-).

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы