Ответ 1
Эта проблема не имеет ничего общего с UTF-8, учитывая, что передаваемые данные, как показано в пакете передачи (внизу вопроса), это UTF-16 Little Endian (как ожидал SQL Server). И это отлично подходит для UTF-16LE, за исключением отсутствующего финального байта, как и сообщение об ошибке.
Проблема, скорее всего, является незначительной ошибкой в freetds, которая неправильно применяет логику, предназначенную для отсечения конечных пробелов из полей строки переменной длины. Вы говорите, нет конечных пробелов? Хорошо, если бы он не был отрублен, это было бы немного яснее (но если бы он не был отрублен, не было бы этой ошибки). Итак, давайте посмотрим, что пакет видит, можем ли мы его восстановить.
Ошибка в данных, вероятно, игнорируется, потому что пакет содержит четное количество байтов. Но не все поля имеют двоичный байт, поэтому не обязательно иметь четное число. Если мы знаем, какие хорошие данные (до ошибки), то мы можем найти начальную точку в данных и двигаться вперед. Лучше всего начинать с Ţ
, поскольку он, мы надеемся, будет выше значения 255/FF и, следовательно, займет 2 байта. Все, что ниже, будет иметь 00
, и многие из символов имеют это с обеих сторон. Хотя мы должны иметь возможность предполагать кодировку Little Endian, лучше знать наверняка. Для этого нам нужен хотя бы один символ с двумя байтами не 00
и байтами, которые отличаются (один из символов 01
для обоих байтов, и это не помогает определить порядок). Первый символ этого строкового поля, Ţ
, подтверждает это, поскольку код Code 0162 показывается как 62 01
в пакете.
Ниже приведены символы в том же порядке, что и пакет, их значения UTF-16 LE и ссылка на их полную информацию. Первая символьная байтовая последовательность 62 01
дает нам отправную точку, поэтому мы можем игнорировать начальную 00 13 00
строки 0040
(они были удалены в приведенной ниже копии для удобочитаемости). Обратите внимание, что "перевод", показанный справа, не интерпретирует Юникод, поэтому 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....|
-
Ţ
-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
, как может показаться, если вы читаете пакет, начинающийся в конце. Так или иначе, последний байт - hex 20
- отсутствует, поэтому ошибка Unicode data is odd byte size
.
Теперь, 20
сам по себе или за которым следует 00
, это пробел. Это объясняет, почему @GordThompson смог заставить его работать, добавив дополнительный символ в конец (последний символ больше не был поддающимся обработке). Это может быть дополнительно доказано, заканчивая другим символом, который является кодовой точкой U + 20xx. Например, если я прав об этом, то завершение с помощью ⁄
- Fraction Slash U + 2044 - будет иметь ту же ошибку, заканчивая ⅄
- Повернутый Sans-Serif Capital Y U + 2144 - даже с ‘
непосредственно перед ним, должен отлично работать (@GordThompson был достаточно любезен, чтобы доказать, что завершение с помощью ⅄
действительно работало, а завершение с помощью ⁄
приводило к той же ошибке).
Если входной файл null
(т.е. 00
) завершен, то это может быть просто конечная последовательность 20 00
, которая делает это, и в этом случае завершение с помощью новой строки может исправить это. Это также можно проверить, протестировав файл с двумя строками: строка 1 - это существующая строка из bad.txt, а строка 2 - это строка, которая должна работать. Например:
291054 Ţawī Rifā‘
999999 test row, yo!
Если работает двухстрочный файл, показанный непосредственно выше, это доказывает, что это комбинация кодовой точки U + 20xx и что кодовая точка является последним символом (передачи больше, чем файла), который выдает ошибку, НО, если этот двухстрочный файл также получит ошибку, то это доказывает, что проблема с кодовой точкой U + 20xx как последним символом строкового поля является проблемой (и было бы разумно предположить, что эта ошибка произойдет, даже если поле строки не было окончательным полем строки, так как в этом случае в этом случае исключается нулевой ограничитель для передачи).
Кажется, это либо ошибка с freetds/freebcp, либо, возможно, есть опция конфигурации, чтобы не пытаться обрезать конечные пробелы или, может быть, способ заставить это поле выглядеть как NCHAR
вместо NVARCHAR
.
UPDATE
Оба @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 дополнительных символов C) и эти Collations были представлены в SQL Server 2012.