Nei*_*gan 12 sql-server unicode character-encoding freetds sql-server-2012
这个文件工作正常(UTF-8):
$ cat ok.txt
291054 ?aw? Rif?
Run Code Online (Sandbox Code Playgroud)
此文件导致错误(UTF-8):
$ cat bad.txt
291054 ?aw? Rif?‘
Run Code Online (Sandbox Code Playgroud)
这是消息:
$ freebcp 'DB.dbo.table' in bad.txt ... -c
Starting copy...
Msg 20050, Level 4
Attempt to convert data stopped by syntax error in source field
Msg 4895, Level 16, State 2
Server '...', Line 1
Unicode data is odd byte size for column 2. Should be even byte size.
Msg 20018, Level 16
General SQL Server error: Check messages from the SQL Server
Run Code Online (Sandbox Code Playgroud)
唯一的区别是最后一个字符,即unicode 2018(左单引号)
知道是什么导致了这个错误吗?
SQL Server使用UTF-16LE(尽管TDS以UCS-2LE开头并切换到我相信)
有问题的专栏是 nvarchar(200)
这是错误之前发送的数据包:
packet.c:741:Sending packet
0000 07 01 00 56 00 00 01 00-81 02 00 00 00 00 00 08 |...V.... ........|
0010 00 38 09 67 00 65 00 6f-00 6e 00 61 00 6d 00 65 |.8.g.e.o .n.a.m.e|
0020 00 69 00 64 00 00 00 00-00 09 00 e7 90 01 09 04 |.i.d.... ...ç....|
0030 d0 00 34 04 6e 00 61 00-6d 00 65 00 d1 ee 70 04 |Ð.4.n.a. m.e.Ñîp.|
0040 00 13 00 62 01 61 00 77-00 2b 01 20 00 52 00 69 |...b.a.w .+. .R.i|
0050 00 66 00 01 01 18 - |.f....|
Run Code Online (Sandbox Code Playgroud)
Gor*_*son 12
更新:此问题显然已在2016-11-04发布的FreeTDS v1.00.16中得到修复.
我可以使用FreeTDS v1.00.15重现您的问题.freebcp
当文本字段的最后一个字符具有表单的Unicode代码点时,它肯定看起来像一个导致它失败的错误U+20xx
.(感谢@srutzky纠正我对原因的结论.)如你所知,这有效......
291054 ?aw? Rif?
Run Code Online (Sandbox Code Playgroud)
......这失败了......
291054 ?aw? Rif?‘
Run Code Online (Sandbox Code Playgroud)
...但我发现这也有效:
291054 ?aw? Rif?‘x
Run Code Online (Sandbox Code Playgroud)
所以,一个丑陋的解决方法是运行针对您的输入文件中的脚本,将低阶非空格Unicode字符追加到每个文本字段(例如,x
这是U+0078
,如上面的最后一个例子),使用freebcp
上传数据,然后UPDATE
针对导入的行运行语句以去除多余的字符.
就个人而言,我会倾向于从freetds的切换到微软的SQL Server ODBC驱动程序适用于Linux,其中包括bcp
和sqlcmd
使用此处介绍的说明安装时的实用程序:
https://gallery.technet.microsoft.com/scriptcenter/SQLCMD-and-BCP-for-Ubuntu-c88a28cc
我刚刚在Xubuntu 16.04下对它进行了测试,虽然我不得不稍微调整一下这个程序libssl.so.1.0.0
而不是libssl.so.0.9.8
(并且相同libcrypto
),一旦我安装了它bcp
,微软的实用程序就会成功freebcp
失败.
如果用于Linux的SQL Server ODBC驱动程序无法在Mac上运行,那么另一种替代方法是使用Microsoft JDBC驱动程序6.0 for SQL Server和一些Java代码,如下所示:
connectionUrl = "jdbc:sqlserver://servername:49242"
+ ";databaseName=myDb"
+ ";integratedSecurity=false";
String myUserid = "sa", myPassword = "whatever";
String dataFileSpec = "C:/Users/Gord/Desktop/bad.txt";
try (
Connection conn = DriverManager.getConnection(connectionUrl, myUserid, myPassword);
SQLServerBulkCSVFileRecord fileRecord = new SQLServerBulkCSVFileRecord(dataFileSpec, "UTF-8", "\t", false);
SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(conn)) {
fileRecord.addColumnMetadata(1, "col1", java.sql.Types.NVARCHAR, 50, 0);
fileRecord.addColumnMetadata(2, "col2", java.sql.Types.NVARCHAR, 50, 0);
bulkCopy.setDestinationTableName("dbo.freebcptest");
bulkCopy.writeToServer(fileRecord);
} catch (Exception e) {
e.printStackTrace(System.err);
}
Run Code Online (Sandbox Code Playgroud)
这个问题与UTF-8无关,因为正在传输的数据,如传输数据包(问题的底部)所示是UTF-16 Little Endian(正如SQL Server所期望的那样).它是完美的UTF-16LE,除了丢失的最终字节外,就像错误消息所暗示的那样.
问题很可能是freetds中的一个小错误,它错误地应用逻辑意味着从可变长度字符串字段中去除尾随空格.你说没有尾随空格吗?好吧,如果它没有被切断那么它会更清楚(但是,如果它没有被切断,那么就不会有这个错误).那么,让我们看一下如果我们可以重建它就会看到什么数据包.
数据中的错误可能被忽略,因为数据包包含偶数个字节.但并非所有字段都是双字节的,因此它不需要是偶数.如果我们知道好的数据是什么(在错误之前),那么我们可以在数据中找到起点并向前移动.最好从?
有望开始,因为它有望高于255/FF值,因此需要2个字节.下面的任何内容都会有一个,00
而且很多角色都有这两个角色.虽然我们应该能够采用Little Endian编码,但最好知道.为此,我们需要至少一个具有两个非00
字节的字符,以及不同的字节(其中一个字符01
用于两个字节,并且无助于确定排序).此字符串字段的第一个字符?
确认了这一点,因为它是Code Point 0162,但62 01
在数据包中显示.
下面是字符,与数据包的顺序相同,它们的UTF-16 LE值以及指向其完整详细信息的链接.第一个字符的字节序列62 01
为我们提供了起点,因此我们可以忽略行的初始00 13 00
值0040
(为了便于阅读,它们已在下面的副本中删除).请注意,右侧显示的"翻译"不解释Unicode,因此2字节序列62 01
显示为62
单独(即小写拉丁语"b")和01
单独显示(即不可打印的字符;显示为"").
0040 xx xx xx 62 01 61 00 77-00 2b 01 20 00 52 00 69 |...b.a.w .+. .R.i|
0050 00 66 00 01 01 18 ?? - |.f....|
Run Code Online (Sandbox Code Playgroud)
?
- 62 01
- http://unicode-table.com/en/0162/a
- 61 00
- http://unicode-table.com/en/0061/w
- 77 00
- http://unicode-table.com/en/0077/?
- 2B 01
- http://unicode-table.com/en/012B/
-- 20 00
- http://unicode-table.com/en/0020/R
- 52 00
- http://unicode-table.com/en/0052/i
- 69 00
- http://unicode-table.com/en/0069/f
- 66 00
- http://unicode-table.com/en/0066/?
- 01 01
- http://unicode-table.com/en/0101/‘
- 18 20
- http://unicode-table.com/en/2018/如您所见,最后一个字符确实是18 20
(即20 18
由于Little Endian编码而进行字节交换),而不是01 18
从最后开始读取数据包时可能出现的字符.不知何故,最后的字节 - 十六进制20
- 缺失,因此Unicode data is odd byte size
错误.
现在,20
它本身或其后00
是一个空间.这可以解释为什么@GordThompson能够通过在末尾添加一个额外的字符来使其工作(最终字符不再可修剪).这可以通过以另一个U + 20xx代码点的字符结尾来进一步证明.例如,如果我对这个正确的,那么结尾?
- 分数斜线U + 2044 -将有同样的错误,而结尾?
- 拍转入无衬线资本ŸU + 2144 -甚至与‘
之前它,应该工作得很好(@GordThompson非常友好地证明以?
完成工作结束,并以?
结果导致同样的错误).
如果输入文件被null
(即00
)终止,那么它可能只是20 00
执行它的结束序列,在这种情况下,以换行符结尾可能会修复它.这也可以通过测试具有两行的文件来证明:第1行是bad.txt中的现有行,第2行是应该工作的行.例如:
291054 ?aw? Rif?‘
999999 test row, yo!
Run Code Online (Sandbox Code Playgroud)
如果上面显示的双行文件有效,则证明它是U + 20xx代码点的组合,并且该代码点是暴露该错误的最后一个字符(传输的多于文件).但是,如果这个双行文件也得到了错误,那么它证明了将U + 20xx代码点作为字符串字段的最后一个字符就是问题(并且假设即使这个错误发生也是合理的.字符串字段不是行的最后一个字段,因为在这种情况下已经排除了传输的空终止符).
这似乎是freetds/freebcp的一个错误,或者可能有一个配置选项,不让它尝试修剪尾随空格,或者可能是让它看到这个字段而NCHAR
不是NVARCHAR
.
@GordThompson和OP(@NeilMcGuigan)都测试并确认无论字符串字段在文件中的哪个位置都存在此问题:在行的中间,行的末尾,最后一行,而不是在最后一排.因此,这是一个普遍的问题.
事实上,我找到了源代码,因为没有考虑多字节字符集,所以会发生问题.我将在GitHub存储库上提交一个问题.该rtrim
函数的来源是:
https://github.com/FreeTDS/freetds/blob/master/src/dblib/bcp.c#L2267
关于这个声明:
SQL Server使用UTF-16LE(尽管TDS以UCS-2LE开头并切换到我相信)
从编码角度来看,UCS-2和UTF-16之间确实没有区别.字节序列是相同的.唯一的区别在于代理对的解释(即U + FFFF/65535之上的代码点).UCS-2具有用于构建代理对保留的代码点,但当时没有任何代理对的实现.UTF-16只是添加了代理对的实现,以便创建补充字符.因此,SQL Server可以毫无问题地存储和检索UTF-16 LE数据.唯一的问题是内置函数不知道如何解释代理对,除非Collation结束_SC
(对于S upplementary C haracters),并且这些Collations是在SQL Server 2012中引入的.