SQL CLR - TSQL UDF 上的权限错误,即使用户具有访问权限

SQL CLR - Permission Error on TSQL UDF Even Though User Has Access

我在 SQL CLR 标量函数中使用上下文连接。该函数构建一个动态查询,然后执行它(从各个地方采购 data/logic 来构建查询)。

我可以手动 "build" 动态字符串,然后 运行 在标准的 Management Studio window 中 运行 它 运行 没问题。当通过上下文连接上的 CLR 执行相同的查询时,我收到一个错误。

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

EXECUTE permission denied on be.udfDivide

我知道我可以访问该 UDF,因为它在我手动 运行 查询时有效 - 虽然当 运行 通过 SQLCLR 时它失败了,即使 SQLCLR 代码应该在我的上下文中执行。

查询有效:

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

<some stuff> 是所有复杂逻辑所在的地方,但我向你保证,它只是一个 where 子句,而当没有 UDF 时,这个 CLR 函数每天成功执行 运行s 数百万次在查询中引用。

通过一些有创意的查询黑客攻击,我已经能够确认上下文连接是 运行 在正确的用户(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

这里发生的事情在我能找到的任何地方都没有记录,但似乎需要在 SqlFunction 属性中指定 SystemDataAccess = SystemDataAccessKind.Read(默认设置为 None) 当执行包含 T-SQL 标量 UDF 或多语句 TVF 的查询时,但是 only 如果函数是 not schema-边界。意思是,如果引用的函数是:

,您可以使用 SystemDataAccess = SystemDataAccessKind.None
  • 内联 TVF
  • 使用 WITH SCHEMABINDING 选项创建的标量 UDF 或多语句 TVF

内联 TVF 没有问题是有道理的,因为它们的定义被拉入引用它们的查询中,并且它们不作为单独的对象调用。

我假设要求对非架构绑定 UDF 和多语句 TVF 进行 "system" 数据访问的原因是系统目录视图可能需要在 运行 时间检查为了验证任何依赖关系是否仍然存在并且权限是否有效,因为由于缺少模式绑定(这可以防止对作为其他对象的依赖关系的对象进行某些更改),无法保证它们与最初的相同。

考虑到这一点,最快/最直接的解决方法(允许 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 通常是解决这个问题的方法。您只需要表达与单个查询相同的逻辑(对 "if-then-else" 使用 CASE),然后通过 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 快 :-)。