SQL 基于 .net ComputeHash 的 CLR 函数不适用于西里尔文
SQL CLR function based on .net ComputeHash is not working with Cyrrilic
我写了下面的 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
,尽管代码 returns 值是作为控制台应用程序执行的。谁能告诉我做错了什么?
此外,我从 here 那里得到了函数,其中一条评论说:
Careful with CLR SP parameters being silently truncated to 8000 bytes
- I had to tag the parameter with [SqlFacet(MaxSize = -1)] otherwise bytes after the 8000th would simply be ignored!
但我已经对此进行了测试并且它工作正常。例如,如果我生成 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服务器没有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 字节限制:
For SQL Server 2014 and earlier, allowed input values are limited to 8000 bytes.
有关解释您为何看到差异的详细演练,请参阅我对以下问题的回答:
对于不想自己编译和部署它的任何人,此功能在 SQL# SQLCLR 函数、存储过程等库的免费版本中可用(其中我是的创造者,但 Util_Hash 和 Util_HashBinary,以及许多其他的是免费的)。问题中显示的内容与 SQL# 中的两个 Util_Hash* 函数有一个区别:问题中显示的函数采用 NVARCHAR
/ SqlString
输入参数,而 SQL# 函数采用 VARBINARY
/ SqlBinary
输入。差异是:
- 接受
VARBINARY
输入也适用于二进制源数据(文件、图像、加密值等)
- 虽然接受
VARBINARY
输入确实需要在函数调用中执行 CONVERT(VARBINARY(MAX), source_string)
的额外步骤,但这样做会保留用于 VARCHAR
数据的任何代码页。虽然不经常使用,但在处理非 Unicode 数据时会很方便。
关于来自其他 post 的警告:
Careful with CLR SP parameters being silently truncated to 8000 bytes - I had to tag the parameter with [SqlFacet(MaxSize = -1)] otherwise bytes after the 8000th would simply be ignored!
但您没有遇到同样的事情:这是由于 SSDT 为 SQLCLR 对象生成 T-SQL 包装器对象的方式发生了变化。在早期版本中(尤其是那些在 VS 2013 之前带有 Visual Studio 的版本),默认行为是对 SqlChars
使用 NVARCHAR(MAX)
,对 SqlString
使用 NVARCHAR(4000)
.但是后来在某些时候(我不想说从 VS 2013 开始,因为 Visual Studio 和 SSDT 是独立的产品,即使 VS 带有 SSDT)默认值被更改为对两者都使用 NVARCHAR(MAX)
SqlChars
和 SqlString
。 post 发出警告的人(2013 年 2 月 6 日)一定一直在使用早期版本的 SSDT。尽管如此,明确并使用 [SqlFacet(MaxSize = -1)]
.
并没有什么坏处(甚至是一个好习惯)
关于 if (algorithmType == null || value.IsNull)
逻辑:因为 NULL
中的任何一个都应该 return 和 NULL
,您最好删除该逻辑并使用 WITH RETURNS NULL ON NULL INPUT
CREATE FUNCTION
语句的选项。然而,不幸的是,这个选项不受任何 SSDT 构造的支持(即没有 SqlFacet
)。因此,为了启用此选项,您可以创建一个 Post-Deployment SQL 脚本(它将在主脚本之后自动部署),它会发出具有所需定义的 ALTER FUNCTION
。投票支持我的 Connect 建议以本机支持此选项不会有什么坏处:Implement OnNullCall property in SqlFunctionAttribute for RETURNS NULL ON NULL INPUT 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 开始,现在可以 use UTF-8 natively via the _UTF8
collations。但是,您仍然无法将 UTF-8 字符 字符串传递给 SQLCLR 对象,因为 SQLCLR API只处理 NVARCHAR
而不是 VARCHAR
。因此,尝试传入 'UTF-8 encoded string'
仍将作为 UTF-16 LE 通过,因为它会在传入的过程中被隐式转换。将 UTF-8 编码字符转换为 SQLCLR 的唯一方法是首先将它们转换为 VARBINARY
并将这些字节传递给 SQLCLR 对象(如 VARBINARY
-> SqlBinary
/ SqlBytes
)。
我写了下面的 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
,尽管代码 returns 值是作为控制台应用程序执行的。谁能告诉我做错了什么?
此外,我从 here 那里得到了函数,其中一条评论说:
Careful with CLR SP parameters being silently truncated to 8000 bytes - I had to tag the parameter with [SqlFacet(MaxSize = -1)] otherwise bytes after the 8000th would simply be ignored!
但我已经对此进行了测试并且它工作正常。例如,如果我生成 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服务器没有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 字节限制:
For SQL Server 2014 and earlier, allowed input values are limited to 8000 bytes.
有关解释您为何看到差异的详细演练,请参阅我对以下问题的回答:
对于不想自己编译和部署它的任何人,此功能在 SQL# SQLCLR 函数、存储过程等库的免费版本中可用(其中我是的创造者,但 Util_Hash 和 Util_HashBinary,以及许多其他的是免费的)。问题中显示的内容与 SQL# 中的两个 Util_Hash* 函数有一个区别:问题中显示的函数采用 NVARCHAR
/ SqlString
输入参数,而 SQL# 函数采用 VARBINARY
/ SqlBinary
输入。差异是:
- 接受
VARBINARY
输入也适用于二进制源数据(文件、图像、加密值等) - 虽然接受
VARBINARY
输入确实需要在函数调用中执行CONVERT(VARBINARY(MAX), source_string)
的额外步骤,但这样做会保留用于VARCHAR
数据的任何代码页。虽然不经常使用,但在处理非 Unicode 数据时会很方便。
关于来自其他 post 的警告:
Careful with CLR SP parameters being silently truncated to 8000 bytes - I had to tag the parameter with [SqlFacet(MaxSize = -1)] otherwise bytes after the 8000th would simply be ignored!
但您没有遇到同样的事情:这是由于 SSDT 为 SQLCLR 对象生成 T-SQL 包装器对象的方式发生了变化。在早期版本中(尤其是那些在 VS 2013 之前带有 Visual Studio 的版本),默认行为是对 SqlChars
使用 NVARCHAR(MAX)
,对 SqlString
使用 NVARCHAR(4000)
.但是后来在某些时候(我不想说从 VS 2013 开始,因为 Visual Studio 和 SSDT 是独立的产品,即使 VS 带有 SSDT)默认值被更改为对两者都使用 NVARCHAR(MAX)
SqlChars
和 SqlString
。 post 发出警告的人(2013 年 2 月 6 日)一定一直在使用早期版本的 SSDT。尽管如此,明确并使用 [SqlFacet(MaxSize = -1)]
.
关于 if (algorithmType == null || value.IsNull)
逻辑:因为 NULL
中的任何一个都应该 return 和 NULL
,您最好删除该逻辑并使用 WITH RETURNS NULL ON NULL INPUT
CREATE FUNCTION
语句的选项。然而,不幸的是,这个选项不受任何 SSDT 构造的支持(即没有 SqlFacet
)。因此,为了启用此选项,您可以创建一个 Post-Deployment SQL 脚本(它将在主脚本之后自动部署),它会发出具有所需定义的 ALTER FUNCTION
。投票支持我的 Connect 建议以本机支持此选项不会有什么坏处:Implement OnNullCall property in SqlFunctionAttribute for RETURNS NULL ON NULL INPUT 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 开始,现在可以 use UTF-8 natively via the _UTF8
collations。但是,您仍然无法将 UTF-8 字符 字符串传递给 SQLCLR 对象,因为 SQLCLR API只处理 NVARCHAR
而不是 VARCHAR
。因此,尝试传入 'UTF-8 encoded string'
仍将作为 UTF-16 LE 通过,因为它会在传入的过程中被隐式转换。将 UTF-8 编码字符转换为 SQLCLR 的唯一方法是首先将它们转换为 VARBINARY
并将这些字节传递给 SQLCLR 对象(如 VARBINARY
-> SqlBinary
/ SqlBytes
)。