Сжатие string в C# и её decompress в mssql
В чём суть моей задачи: у меня есть список объектов, который неоходимо сериализовать в json и отправить его в другой сервис через вызов хранимой процедуры. Сериализуемые объекты, содержат свойство Body являющееся массивом byte. Значением этого свойства является сжатая строка. В другом сервисе она сохраняется в базу данных, если будет необходимость узнать изначальный текст, то должна быть возможность "разжать" её при чтении.
Проблема: выполняю сжатие начального текста с помощью GZip, сериализую объект в json вызываю хранимую процедуру, в базе данных всё сохраняется, но выполнить DECOMPRESS не получается. Выдаётся следующая ошибка: "SQL Error [9826] [S0001]: В качестве аргумента во встроенную инструкцию DECOMPRESS переданы несжатые или поврежденные данные."
Сравнил результат работы функции COMPRESS в SQL и C#, сжатые данные отличаются (определил по количеству байтов после сжатия). Код, который использую для сжатия:
public static async Task<byte[]> CompressString(string source)
{
byte[] bytes = Encoding.Unicode.GetBytes(source);
using (var memoryStream = new MemoryStream())
{
using (var gzipStream = new GZipStream(memoryStream, CompressionLevel.Optimal))
{
await gzipStream.WriteAsync(bytes);
}
return memoryStream.ToArray();
}
}
Менял Unicode на UTF8, UTF32 и так далее. Ещё вероятно проблема при сериализации. На сколько мне известно массив байт при сериализации в C# преобразуется в формат Base64String.
Код с помощью которого пытаются "разжать" строку на стороне SQL:
SELECT TOP 1 body_bin, CAST(DECOMPRESS(body_bin) AS nvarchar(max))
FROM dbo.Message m
ORDER BY m.key DESC
Я если честно не знаю, как в эту таблицу попадают данные, так как это не моя зона ответственности.
Для проверки работы, я выполнял сжатие с помощью приведённого выше кода, а также сериализовывать полученные байты в Base64String, как в коде ниже:
var base64CompressedBody = Convert.ToBase64String((await GZipCompressor.CompressString(message.Body)));
Дальше я копировал полученное значение из base64CompressedBody и пытался выполнить DECOMPRESS:
declare @val varchar(max), @value varbinary(max), @decompressed varchar(max);
set @val = 'H4sIAAAAAAAACh2MSw7CQAxDvfAJumI5B0DVaNrQz9BCERIsKnH/2/DaRWL5xU7j0S9nV3Ry0sUFt3hFR398VVJRp1ZZE7ugGZ/UkPsySQO7p1F9c9A92htfd24PyJPpyQRs8w86n+kKmWEriey7W74et/AbGpDwH3TJC92gAAAA'
set @value = cast(@val as varbinary(max));
set @decompressed = CAST(DECOMPRESS(@value) AS varchar(max));
select @value, @decompressed
Этот код не выполняется, выскакивает та же SQL ошибка: "SQL Error [9826] [S0001]: В качестве аргумента во встроенную инструкцию DECOMPRESS переданы несжатые или поврежденные данные."
Кто-нибудь знает, как можно решить эту проблему?
Ответы (1 шт):
По итогу...
Ф-ция CompressString отрабатывает хорошо, даёт результат идеинтичный COMPRESS
base64encode запаковано правильно.
Вы пытаетесь base64 сделать decompress, это нельзя делать. Как складывали "матрёшку" так и разбираем - сначало base64decode а только потом decompress.
Решаю вашу задачу. Находим код "decode base64 mssql"
Берем код тут https://dba.stackexchange.com/questions/191273/decode-base64-string-natively-in-sql-server
И немного вправим мозги ему
Алгоримт - берем ваш base64 распаковываем-преобразуем в байты. Потом делаем decompress этим байтам, то что получилось - кастим в строку.
SELECT
cast(
DECOMPRESS(
CONVERT
(
VARBINARY(MAX),
CAST('' AS XML).value('xs:base64Binary(sql:column("BASE64_COLUMN"))', 'VARBINARY(MAX)')
) ) as nvarchar(max))AS RESULT
FROM
(
--- ваш @val
SELECT 'H4sIAAAAAAAACh2MSw7CQAxDvfAJumI5B0DVaNrQz9BCERIsKnH/2/DaRWL5xU7j0S9nV3Ry0sUFt3hFR398VVJRp1ZZE7ugGZ/UkPsySQO7p1F9c9A92htfd24PyJPpyQRs8w86n+kKmWEriey7W74et/AbGpDwH3TJC92gAAAA' AS BASE64_COLUMN
) A
Но я бы так не делал, base64 расходует много места.
Я бы передал blob в параметрах и просто сделал SELECT cast( DECOMPRESS(@param1) as nvarchar(max)) возможно это не всегда возможно - тогда делайте как выше.
P.S.
select Compress('AAA') - запакует 3 байта в формате ANSI т.е. varchar. Применив collate можно выкрутиться с кодировкой. Utf8 - неподскажу, прямо конвертить умеет с mssql 2019, можно конвертировать обходным путём но это 3-х этажные выражения будут.
select Compress(N'AAA') - запакует 6 байт в формате UNICODE т.е. nvarchar.
Соответственно тут ANSI/UNICODE - это один из шагов где можно запутаться. Скорее всего несовпадение байт было связано с тем, что перепутали кодировку. Плюс кодировка base64 вносит немного путаницы.