基于 .net ComputeHash 的 SQL CLR 函数不适用于 Cyrrilic
本文关键字:函数 不适用 适用于 Cyrrilic CLR SQL net ComputeHash 基于 | 更新日期: 2023-09-27 18:33:50
我编写了以下SQL CLR
函数,以便对大于 8000 字节的字符串值进行哈希处理(T-SQL
内置HASHBYTES
函数的输入值限制(:
[SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true)]
public static SqlBinary HashBytes(SqlString algorithm, SqlString value)
{
HashAlgorithm algorithmType = HashAlgorithm.Create(algorithm.Value);
if (algorithmType == null || value.IsNull)
{
return new SqlBinary();
}
else
{
byte[] bytes = Encoding.UTF8.GetBytes(value.Value);
return new SqlBinary(algorithmType.ComputeHash(bytes));
}
}
它适用于拉丁字符串。例如,以下哈希是相同的:
SELECT dbo.fn_Utils_GetHashBytes ('MD5', 'test'); -- 0x098F6BCD4621D373CADE4E832627B4F6
SELECT HASHBYTES ('MD5', 'test'); -- 0x098F6BCD4621D373CADE4E832627B4F6
问题是它不适用于西里尔字符串。例如:
SELECT dbo.fn_Utils_GetHashBytes ('MD5 ', N'даровете на влъхвите') -- NULL
SELECT HashBytes ('MD5 ',N'даровете на влъхвите') -- 0x838B1B625A6074B2BE55CDB7FCEA2832
SELECT dbo.fn_Utils_GetHashBytes ('SHA256', N'даровете на влъхвите') -- 0xA1D65374A0B954F8291E00BC3DD9DF655D8A4A6BF127CFB15BBE794D2A098844
SELECT HashBytes ('SHA2_256',N'даровете на влъхвите') -- 0x375F6993E0ECE1864336E565C8E14848F2A4BAFCF60BC0C8F5636101DD15B25A
我正在为MD5
NULL
,尽管如果代码作为控制台应用程序执行,则代码返回值。谁能说出我做错了什么?
另外,我从这里获得了该功能,其中一条评论说:
小心将 CLR SP 参数静默截断为 8000 字节 - 我必须用 [SqlFacet(MaxSize = -1(] 标记参数,否则第 8000 个之后的字节将被忽略!
但是我已经对此进行了测试,并且工作正常。例如,如果我生成一个 8000 字节字符串的哈希和相同字符串的第二个哈希加上一个符号,则得到的哈希值不同。
DECLARE @A VARCHAR(MAX) = '8000 bytes string...'
DECLARE @B VARCHAR(MAX) = @A + '1'
SELECT LEN(@A), LEN(@B)
SELECT IIF(dbo.fn_Utils_GetHashBytes ('MD5', @A + '1') = dbo.fn_Utils_GetHashBytes ('MD5', @B), 1, 0) -- 0
我应该担心这个吗?
Encoding.UTF8.GetBytes(...)
SQL Server没有UTF-8的概念。使用 UCS-2 (UTF-16( 或 ASCII。使用的编码必须与您传递给HASHBYTES
的编码相匹配。您可以轻松看到HASHBYTES
哈希VARCHAR
与 NVARCHAR
:
select HASHBYTES('MD5', 'Foo') -- 0x1356C67D7AD1638D816BFB822DD2C25D
select HASHBYTES('MD5', N'Foo') -- 0xB25FF0AD90D09D395090E8A29FF4C63C
最好是更改 SQLCLR 函数以接受字节而不是字符串,并处理要在调用方中VARBINARY
的强制转换。
SELECT dbo.fn_Utils_GetHashBytes ('MD5', CAST(N'даровете на влъхвите' AS VARBINARY(MAX));
仅供参考,SQL Server 2016已经取消了对HASHBYTES
的8000字节限制:
对于 SQL Server 2014 及更早版本,允许的输入值限制为 8000 字节。
有关解释为什么您看到差异的详细演练,请参阅我对以下问题的回答:
TSQL md5 哈希不同于 C# .NET md5
对于任何不希望自己编译和部署它的人,这个函数可以在SQL#库的SQLCLR函数,存储过程等的免费版本中找到(我是它的创建者,但Util_Hash和Util_HashBinary,等等,都是免费的(。问题中显示的内容与 SQL# 中的两个 Util_Hash* 函数之间存在一个区别:问题中显示的函数采用NVARCHAR
/SqlString
输入参数,而 SQL# 函数采用VARBINARY
/SqlBinary
输入。区别在于:
- 接受
VARBINARY
输入也适用于二进制源数据(文件、图像、加密值等( - 虽然接受
VARBINARY
输入确实需要在函数调用中执行CONVERT(VARBINARY(MAX), source_string)
的额外步骤,但这样做会保留用于VARCHAR
数据的任何代码页。虽然不经常使用,但在处理非 Unicode 数据时会很方便。
关于其他帖子的警告:
小心将 CLR SP 参数静默截断为 8000 字节 - 我必须用 [SqlFacet(MaxSize = -1(] 标记参数,否则第 8000 个之后的字节将被忽略!
然而,您没有遇到同样的事情:这是由于 SSDT 为 SQLCLR 对象生成 T-SQL 包装器对象的方式发生了变化。在早期版本中(尤其是VS 2013之前随Visual Studio提供的版本(,默认行为是将NVARCHAR(MAX)
用于SqlChars
,NVARCHAR(4000)
用于SqlString
。但是在某个时候(我不想说从VS 2013开始,因为Visual Studio和SSDT是独立的产品,即使VS附带SSDT(默认设置更改为对SqlChars
和SqlString
都使用NVARCHAR(MAX)
。发布警告的人员(2013 年 2 月 6 日(必须使用的是早期版本的 SSDT。尽管如此,明确并使用[SqlFacet(MaxSize = -1)]
并没有什么坏处(甚至是一种很好的做法(。
关于if (algorithmType == null || value.IsNull)
逻辑:由于NULL
任何一个都应该返回一个NULL
,因此最好删除该逻辑并使用CREATE FUNCTION
语句的WITH RETURNS NULL ON NULL INPUT
选项。但是,不幸的是,任何 SSDT 构造都不支持此选项(即没有SqlFacet
(。因此,为了启用此选项,您可以创建一个部署后 SQL 脚本(将在主脚本之后自动部署(,该脚本发出具有所需定义的ALTER FUNCTION
。投票支持我的连接建议以本机支持此选项并没有什么坏处:在 SqlFunctionAttribute 中实现 OnNullCall 属性,用于在空输入 SQLCLR 上返回空值。在实际层面上,性能提升主要体现在您为 @value
参数传入大量值但但不知何故@algorithm
NULL
,因此您最终不会使用 @value
的值。使用 RETURNS NULL ON NULL INPUT
选项的原因是,当您调用传入 SqlString
或 SqlBinary
的 SQLCLR 函数时,整个值将复制到应用程序域的内存中。那是时间、内存和 CPU,如果您提前知道您不会使用它,则无需浪费:-(。即使传入较小的值,您也可能看到非常频繁调用的函数的增益。
关于警告和测试的旁注:SQLCLR 不支持 VARCHAR
,仅支持 NVARCHAR
。因此,从来没有 8000 的限制,因为如果 SSDT 没有自动使用 NVARCHAR(MAX)
,限制将是 4000 个字符。因此,如果存在差异,则首先会看到仅使用 4000 和 4001 个字符进行测试。
更新:从SQL Server 2019开始,现在可以通过_UTF8
排序规则本机使用UTF-8。但是,您仍然无法将 UTF-8 字符串传入 SQLCLR 对象,因为 SQLCLR API 仅处理NVARCHAR
而不处理VARCHAR
。因此,尝试传入'UTF-8 encoded string'
仍将作为 UTF-16 LE 通过,因为它将在传入过程中隐式转换。将 UTF-8 编码字符转换为 SQLCLR 的唯一方法是首先将它们转换为 VARBINARY
并将这些字节传递到 SQLCLR 对象中(如 VARBINARY
-> SqlBinary
/SqlBytes
(。