【问题标题】:SQL CLR - Permission Error on TSQL UDF Even Though User Has AccessSQL CLR - 即使用户具有访问权限,TSQL UDF 上的权限错误
【发布时间】:2017-11-16 21:33:56
【问题描述】:

我在 SQL CLR 标量函数中使用上下文连接。该函数构建一个动态查询,然后执行它(从各个地方获取数据/逻辑以构建查询)。

我可以手动“构建”动态字符串并在标准 Management Studio 窗口中运行它,它运行良好。当通过 CLR 在上下文连接上执行相同的查询时,我遇到了错误。

我调试了这个东西,发现错误是由于查询中调用的 TSQL UDF 的权限问题造成的。产生的错误信息是:

对 be.udfDivide 的执行权限被拒绝

我知道我可以访问该 UDF,因为它在我手动运行查询时工作 - 尽管通过 SQLCLR 运行时它会失败,即使 SQLCLR 代码应该在我的上下文中执行。

查询有效:

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

&lt;some stuff&gt; 是所有复杂逻辑所在的地方,但我向您保证,它只是一个 where 子句,当查询中没有引用 UDF 时,此 CLR 函数每天成功运行数百万次。

通过一些创造性的查询黑客,我已经能够确认上下文连接是在正确的用户(SYSTEM_USERCURRENT_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-server tsql sql-server-2012 user-defined-functions sqlclr


    【解决方案1】:

    这里发生的事情在我能找到的任何地方都没有记录,但似乎需要在执行包含 T- SQL 标量 UDF 或多语句 TVF,但 如果函数是 模式绑定的。意思是,如果引用的函数是,您可以使用SystemDataAccess = SystemDataAccessKind.None

    • 内嵌 TVF
    • 使用WITH SCHEMABINDING 选项创建的标量 UDF 或多语句 TVF

    内联 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 表示“if-then-else”),然后通过CROSS APPLY 而不是在SELECT 列表中使用它,这两者都很容易.

    这是与内联 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 将比当前的标量 UDF 快很多 :-)。

    【讨论】:

    • 包装在 exec 中会产生正确的结果(与 SSMS 中的非动态查询相同的结果)。我们确实有几十个内联表值函数,但这种特殊情况不利于它。编辑问题以提供错误消息。
    • 快速澄清 - 这个特定用例每天只会运行十几次 - 其他不调用 UDF 的查询会运行数百万次
    • 我假设所有这些对象都在同一个数据库中?另外,是否使用了任何模拟? CREATE FUNCTION 定义是否包含EXECUTE AS 子句?通常是EXECUTE AS CALLER。您是作为 Windows 登录名还是 SQL Server 登录名登录的? ORIGINAL_LOGIN() 函数在上下文连接下运行时返回什么?
    • 抱歉耽搁了 - 卷入了其他事情。都在同一个数据库中。在CREATE FUNCTION 中没有指定EXECUTE AS。以 Windows 登录名登录。我是服务器上的sa,这对我不起作用。 ORIGINAL_LOGIN() 在我运行查询时返回我的用户 ID。
    • 太棒了。非常感谢您的彻底回应和调查。我已经能够产生与您相同的结果。感谢您的帮助!
    猜你喜欢
    • 2011-05-05
    • 1970-01-01
    • 1970-01-01
    • 2014-08-11
    • 2020-09-11
    • 1970-01-01
    • 1970-01-01
    • 2011-06-07
    • 2017-11-12
    相关资源
    最近更新 更多