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_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
这里发生的事情在我能找到的任何地方都没有记录,但似乎需要在 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 快 :-)。
我在 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_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
这里发生的事情在我能找到的任何地方都没有记录,但似乎需要在 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 快 :-)。