habrahabr

Микропост: режем UNICODE-строки для PostgreSQL

  • пятница, 21 февраля 2020 г. в 00:27:06
https://habr.com/ru/post/483738/
  • JavaScript
  • PostgreSQL
  • Node.JS


Периодически возникает желание положить в базу «неположимое» — например, засунуть очень длинную строку. Нет, записать ее в поле таблицы — для PostgreSQL проблем нет, но вот в индекс…

Проблема в том, что вся строка (ROW) индекса целиком должна полностью умещаться на одной странице данных (8KB), иначе вас ждет примерно такая ошибка:
ERROR: index row size… exceeds maximum… for index ...
То есть даже в простейшем случае индекса из единственной строки — можно наступить на грабли. Как с ними бороться?

Можно, конечно, сделать функциональный индекс по substring, но тогда это же выражение придется протаскивать и во все запросы, что совсем не добавляет счастья.

Аналогичные грабли ждут нас и при попытке отправить слишком большой (больше 8000 байт) payload в NOTIFY.

Вроде бы все должно быть предельно просто — возьми да отрежь (или порежь дольками, если надо) строку по 8K. Только вот по 8K — байт, а строки-то у нас на JavaScript (подставить язык по вкусу) все сплошь из unicode-символов, которые еще и длину могут иметь в байтах переменную. А заносить в базу «полувалидную» строку как-то совсем нехорошо.

А что будет, если мы строку все-таки порежем «посередине» символа? Тут нам на помощь приходит стандарт UNICODE:
U+FFFD � REPLACEMENT CHARACTER used to replace an unknown, unrecognized or unrepresentable character
Оказывается, если мы попытаемся преобразовать такую «порезаную побайтово» строку обратно, на конце мы получим символ с кодом FFFD:



Итого, если нам целиком вся-вся строка не очень дорога (например, с текстом ошибки, от которого нам важно только начало), подрежем ее до последнего целого символа под требуемый размер в байтах:

const MAX_BYTE_LEN = 8191;
let buf = Buffer.from(errorString);
if (buf.length > MAX_BYTE_LEN) {
  errorString = buf.slice(0, MAX_BYTE_LEN).toString().replace(/\uFFFD$/, '');
}