В операционной системе сервера должны быть установлены локали с поддержкой русского языка:
student$ locale -a|grep ru
ru_RU.koi8r ru_RU.utf8 russian
Инициализация кластера баз данных в виртуальной машине курса выполнялась командой:
postgres$ initdb --locale=ru_RU.utf8 --lc-messages=en_US.utf8
Если при запуске initdb не указывать параметры локализации, то будут использованы те, что установлены в ОС:
student$ locale
LANG=en_US.UTF-8 LANGUAGE=en_US LC_CTYPE="en_US.UTF-8" LC_NUMERIC=ru_RU.UTF-8 LC_TIME=ru_RU.UTF-8 LC_COLLATE="en_US.UTF-8" LC_MONETARY=ru_RU.UTF-8 LC_MESSAGES=en_US.UTF8 LC_PAPER=ru_RU.UTF-8 LC_NAME=ru_RU.UTF-8 LC_ADDRESS=ru_RU.UTF-8 LC_TELEPHONE=ru_RU.UTF-8 LC_MEASUREMENT=ru_RU.UTF-8 LC_IDENTIFICATION=ru_RU.UTF-8 LC_ALL=
Среди категорий локали также присутствуют переменные, имеющие особое значение:
Несколько параметров PostgreSQL соответствуют одноименным категориям локали. Видно что их меньше чем в ОС:
=> SELECT name, setting, context FROM pg_settings WHERE name LIKE 'lc%';
name | setting | context -------------+------------+----------- lc_collate | ru_RU.utf8 | internal lc_ctype | ru_RU.utf8 | internal lc_messages | en_US.utf8 | superuser lc_monetary | ru_RU.utf8 | user lc_numeric | ru_RU.utf8 | user lc_time | ru_RU.utf8 | user (6 rows)
Каждый обслуживающий процесс при запуске сбрасывает значение LC_ALL и устанавливает переменные окружения ОС для категорий локали в соответствии с конфигурационными параметрами "lc%".
При изменении параметра по ходу сеанса, обслуживающий процесс устанавливает это же значение для соответствующей переменной окружения. По значению столбца context понятно, что параметры lc_monetary, lc_numeric и lc_time может изменить любой пользователь, а lc_messages только суперпользователь. Параметры lc_ctype и lc_collate изменить невозможно.
Новые базы данных копируют параметры локализации у используемого шаблона:
=> CREATE DATABASE admin_localization_utf8;
CREATE DATABASE
=> \list admin_localization_utf8|template1
List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges -------------------------+----------+----------+------------+------------+----------------------- admin_localization_utf8 | postgres | UTF8 | ru_RU.utf8 | ru_RU.utf8 | template1 | postgres | UTF8 | ru_RU.utf8 | ru_RU.utf8 | =c/postgres + | | | | | postgres=CTc/postgres (2 rows)
Создадим базу данных с кодировкой KOI8R, а клиент по-прежнему будет использовать UTF8. Чтобы база данных успешно создалась, нужно учесть следующее:
=> CREATE DATABASE admin_localization_koi8r ENCODING = 'KOI8R' LC_COLLATE = 'ru_RU.koi8r' LC_CTYPE = 'ru_RU.koi8r' TEMPLATE = template0;
CREATE DATABASE
=> \l admin_localization_koi8r
List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges --------------------------+----------+----------+-------------+-------------+------------------- admin_localization_koi8r | postgres | KOI8R | ru_RU.koi8r | ru_RU.koi8r | (1 row)
Подключимся к admin_localization_koi8r и убедимся, что работает перекодировка символов.
=> \c admin_localization_koi8r
You are now connected to database "admin_localization_koi8r" as user "postgres".
Кодировки клиента и сервера:
=> SELECT name, setting, context FROM pg_settings WHERE name LIKE '%encoding';
name | setting | context -----------------+---------+---------- client_encoding | KOI8R | user server_encoding | KOI8R | internal (2 rows)
В интерактивном режиме параметр client_encoding принял бы значение кодировки клиента (переменной LC_CTYPE в ОС). Но при запуске из скрипта, как сейчас в демонстрации, он устанавливается в значение кодировки базы данных.
Явно зададим значение такое же как и у клиентской локали:
=> \! locale | grep LC_CTYPE
LC_CTYPE="en_US.UTF-8"
=> SET client_encoding = 'UTF8';
SET
Теперь символы будут правильно конвертироваться из UTF8 в KOI8R и обратно:
=> CREATE TABLE tab AS SELECT 'Привет, мир!'::text AS col;
SELECT 1
=> SELECT * FROM tab;
col -------------- Привет, мир! (1 row)
Список доступных перекодировок для koi8:
=> \dcS *koi8*
List of conversions Schema | Name | Source | Destination | Default? ------------+------------------------+---------------+---------------+---------- pg_catalog | iso_8859_5_to_koi8_r | ISO_8859_5 | KOI8R | yes pg_catalog | koi8_r_to_iso_8859_5 | KOI8R | ISO_8859_5 | yes pg_catalog | koi8_r_to_mic | KOI8R | MULE_INTERNAL | yes pg_catalog | koi8_r_to_utf8 | KOI8R | UTF8 | yes pg_catalog | koi8_r_to_windows_1251 | KOI8R | WIN1251 | yes pg_catalog | koi8_r_to_windows_866 | KOI8R | WIN866 | yes pg_catalog | koi8_u_to_utf8 | KOI8U | UTF8 | yes pg_catalog | mic_to_koi8_r | MULE_INTERNAL | KOI8R | yes pg_catalog | utf8_to_koi8_r | UTF8 | KOI8R | yes pg_catalog | utf8_to_koi8_u | UTF8 | KOI8U | yes pg_catalog | windows_1251_to_koi8_r | WIN1251 | KOI8R | yes pg_catalog | windows_866_to_koi8_r | WIN866 | KOI8R | yes (12 rows)
=> \c admin_localization_utf8
You are now connected to database "admin_localization_utf8" as user "postgres".
При инициализации кластера в системном каталоге каждой базы данных создаются специальные объекты - правила сортировки. Они создаются на основе информации об установленных в ОС локалях.
Правила сортировки для русского языка, полученные из ОС:
=> \dOS+ ru*
List of collations Schema | Name | Collate | Ctype | Provider | Description ------------+-------------+------------+------------+----------+---------------------- pg_catalog | ru-BY-x-icu | ru-BY | ru-BY | icu | Russian (Belarus) pg_catalog | ru-KG-x-icu | ru-KG | ru-KG | icu | Russian (Kyrgyzstan) pg_catalog | ru-KZ-x-icu | ru-KZ | ru-KZ | icu | Russian (Kazakhstan) pg_catalog | ru-MD-x-icu | ru-MD | ru-MD | icu | Russian (Moldova) pg_catalog | ru-RU-x-icu | ru-RU | ru-RU | icu | Russian (Russia) pg_catalog | ru-UA-x-icu | ru-UA | ru-UA | icu | Russian (Ukraine) pg_catalog | ru-x-icu | ru | ru | icu | Russian pg_catalog | ru_RU | ru_RU.utf8 | ru_RU.utf8 | libc | pg_catalog | ru_RU.utf8 | ru_RU.utf8 | ru_RU.utf8 | libc | (9 rows)
Правила сортировки для провайдера ICU создаются только в том случае, если сервер собран с параметром --with-icu:
=> SELECT setting FROM pg_config() WHERE name = 'CONFIGURE';
setting ------------------------------------------------------------------------------ '--prefix=/usr/local/pgsql' '--with-pgport=5432' '--enable-nls' '--with-icu' (1 row)
Сортировка для правила ru-x-icu отличается от используемой по умолчанию сортировки для правила ru_RU. Кириллица идет раньше латиницы:
=> WITH t(c) AS ( VALUES('а'),('б'),('в'),('a'),('b'),('c'),('1'),('2'),('3') ) SELECT string_agg(t.c,',' ORDER BY t.c) AS "default", string_agg(t.c,',' ORDER BY t.c COLLATE "ru-x-icu") AS "ru-x-icu" FROM t \gx
-[ RECORD 1 ]--------------- default | 1,2,3,a,b,c,а,б,в ru-x-icu | 1,2,3,а,б,в,a,b,c
Ключевое слово COLLATE после выражения (в ORDER BY) явно задает правило сортировки. По умолчанию используется правило с именем default, соответствующее параметрам локали базы данных.
Возможности библиотеки ICU позволяют по-разному настраивать сортировку отдельных групп символов.
Создадим новое правило сортировки, где латинские символы идут раньше символов кириллицы:
=> CREATE COLLATION latn_cyrl (provider = icu, locale = 'ru-RU-u-kr-latn-cyrl');
CREATE COLLATION
Еще одно, где в дополнении к предыдущему буквы идут раньше цифр:
=> CREATE COLLATION latn_cyrl_digit (provider = icu, locale = 'ru-RU-u-kr-latn-cyrl-digit');
CREATE COLLATION
Проверяем сортировку:
=> WITH t(c) AS ( VALUES('а'),('б'),('в'),('a'),('b'),('c'),('1'),('2'),('3') ) SELECT string_agg(t.c,',' ORDER BY t.c) AS "default", string_agg(t.c,',' ORDER BY t.c COLLATE "ru-x-icu") AS "ru-x-icu", string_agg(t.c,',' ORDER BY t.c COLLATE "latn_cyrl") AS "latn_cyrl", string_agg(t.c,',' ORDER BY t.c COLLATE "latn_cyrl_digit") AS "latn_cyrl_digit" FROM t \gx
-[ RECORD 1 ]---+------------------ default | 1,2,3,a,b,c,а,б,в ru-x-icu | 1,2,3,а,б,в,a,b,c latn_cyrl | 1,2,3,a,b,c,а,б,в latn_cyrl_digit | a,b,c,а,б,в,1,2,3
При создании правила сортировки ICU в системном каталоге сохраняется версия правила, переданная операционной системой. Если в ОС версия правила изменится, то каждый раз при использовании правила будет выдаваться предупреждение о том, что версии не совпадают.
Проверить соответствие версий можно запросом:
=> SELECT c.collname, c.collversion AS version, pg_collation_actual_version(c.oid) AS actual_version FROM pg_collation c WHERE c.collname LIKE 'ru%' AND c.collprovider = 'i';
collname | version | actual_version -------------+-------------+---------------- ru-BY-x-icu | 153.56.27.1 | 153.56.27.1 ru-KG-x-icu | 153.56.27.1 | 153.56.27.1 ru-KZ-x-icu | 153.56.27.1 | 153.56.27.1 ru-MD-x-icu | 153.56.27.1 | 153.56.27.1 ru-RU-x-icu | 153.56.27.1 | 153.56.27.1 ru-UA-x-icu | 153.56.27.1 | 153.56.27.1 ru-x-icu | 153.56.27.1 | 153.56.27.1 (7 rows)
Изменение правила сортировки может привести к некорректной работе индексов. Если подобное произошло, рекомендуется пересоздать индексы с новой версией правила сортировки. Также следует проверить ограничения CHECK и табличные триггеры на использование измененных правил сортировки. После этого можно обновить версию в системном каталоге командой ALTER COLLATION ... REFRESH VERSION и предупреждения перестанут выдаваться.
Запрос для получения списка объектов, которые зависят от требующих обновления правил сортировки:
=> SELECT pg_describe_object(refclassid, refobjid, refobjsubid) AS "Collation", pg_describe_object(classid, objid, objsubid) AS "Object" FROM pg_depend d JOIN pg_collation c ON refclassid = 'pg_collation'::regclass AND refobjid = c.oid WHERE c.collversion <> pg_collation_actual_version(c.oid) ORDER BY 1, 2;
Collation | Object -----------+-------- (0 rows)
В итоге. К любому выражению, где сортируются или сравниваются текстовые данные, применяется некоторое правило сортировки. В общем случае правило сортировки выражения определяется следующим образом:
Параметр конфигурации, отвечающий за соответствующую категорию локали:
=> SET lc_time = 'ru_RU.UTF8';
SET
Для использования названий месяцев и дней недели на русском языке в форматной маске функции to_char используется префикс TM:
=> SELECT to_char(current_date, 'TMDay, DD TMMonth YYYY');
to_char ----------------------------- Понедельник, 12 Август 2019 (1 row)
То же самое для американского английского:
=> SET lc_time = 'en_US.UTF8';
SET
=> SELECT to_char(current_date, 'TMDay, DD TMMonth YYYY');
to_char ------------------------ Monday, 12 August 2019 (1 row)
Функции to_date/to_char позволяют в явном виде указать формат даты и времени при вводе/выводе значений. На неявное преобразование даты в текст и наоборот влияет параметр datestyle. Вот несколько примеров:
=> SET datestyle = 'Postgres'; SELECT current_setting('datestyle') AS datestyle, now();
SET datestyle | now ---------------+------------------------------------- Postgres, DMY | Mon 12 Aug 17:18:00.743155 2019 MSK (1 row)
=> SET datestyle = 'SQL, DMY'; SELECT current_setting('datestyle') AS datestyle, now();
SET datestyle | now -----------+-------------------------------- SQL, DMY | 12/08/2019 17:18:00.794851 MSK (1 row)
=> SET datestyle = 'SQL, MDY'; SELECT current_setting('datestyle') AS datestyle, now();
SET datestyle | now -----------+-------------------------------- SQL, MDY | 08/12/2019 17:18:00.849333 MSK (1 row)
=> RESET datestyle; SELECT current_setting('datestyle') AS datestyle, now();
RESET datestyle | now -----------+------------------------------- ISO, DMY | 2019-08-12 17:18:00.899257+03 (1 row)
Параметр конфигурации, отвечающий за соответствующую категорию локали:
=> SET lc_numeric = 'ru_RU.UTF8';
SET
Разделители групп разрядов (G), а также целой и дробной части (D), принятые в России:
=> SELECT to_char('12345'::numeric, '999G999D00');
to_char ------------- 12 345,00 (1 row)
То же для США:
=> SET lc_numeric = 'en_US.UTF8';
SET
=> SELECT to_char('12345'::numeric, '999G999D00');
to_char ------------- 12,345.00 (1 row)
Параметр конфигурации, отвечающий за соответствующую категорию локали:
=> SET lc_monetary = 'ru_RU.UTF8';
SET
Денежный тип данных MONEY добавляет к сумме код валюты:
=> SELECT '12345'::money;
money --------------- 12 345.00 руб (1 row)
Но нужно учитывать, что в таблице с таким типом можно хранить только одну валюту, и та будет меняться вместе с LC_MONETARY:
=> SET lc_monetary = 'en_US.UTF8';
SET
=> SELECT '12345'::money;
money ------------ $12,345.00 (1 row)
Сообщения сервера можно выводить на разных языках. Предварительно нужно убедиться, что PostgreSQL собран с поддержкой NLS:
=> SELECT setting FROM pg_config() WHERE name = 'CONFIGURE';
setting ------------------------------------------------------------------------------ '--prefix=/usr/local/pgsql' '--with-pgport=5432' '--enable-nls' '--with-icu' (1 row)
Список доступных языков и файлы сообщений здесь:
=> SELECT setting FROM pg_config() WHERE name = 'LOCALEDIR';
setting ------------------------------- /usr/local/pgsql/share/locale (1 row)
postgres$ ls -C /usr/local/pgsql/share/locale
cs de es fr he it ja ko nb pl pt_BR ro ru sv ta tr zh_CN zh_TW
Для записи в журнал будем использовать ошибочную команду:
=> select1;
ERROR: syntax error at or near "select1" LINE 1: select1; ^
postgres$ tail -n 2 /home/postgres/logfile
2019-08-12 17:18:01.383 MSK [15901] ERROR: syntax error at or near "select1" at character 1 2019-08-12 17:18:01.383 MSK [15901] STATEMENT: select1;
В журнал сервера записывается такое же сообщение об ошибке и сама ошибочная команда.
Для вывода сообщений на русском языке надо изменить параметр lc_messages.
Однако если при старте сервера установлена переменная LANGUAGE, то сообщения сервера будут выдаваться именно на этом языке, а параметр lc_messages будет игнорироваться:
postgres$ echo $LANGUAGE
en_US
=> SET lc_messages TO 'ru_RU.UTF8';
SET
=> select1;
ERROR: syntax error at or near "select1" LINE 1: select1; ^
Перезапустим сервер, сбросив переменную среды LANGUAGE.
=> \q
postgres$ pg_ctl -w -D /usr/local/pgsql/data stop
waiting for server to shut down.... done server stopped
postgres$ unset LANGUAGE
postgres$ pg_ctl -w -l /home/postgres/logfile -D /usr/local/pgsql/data start
waiting for server to start.... done server started
student$ psql -d admin_localization_utf8
=> SET lc_messages TO 'ru_RU.UTF8';
SET
=> select1;
ОШИБКА: ошибка синтаксиса (примерное положение: "select1") LINE 1: select1; ^
postgres$ tail -n 2 /home/postgres/logfile
2019-08-12 17:18:01.901 MSK [16854] ОШИБКА: ошибка синтаксиса (примерное положение: "select1") (символ 1) 2019-08-12 17:18:01.901 MSK [16854] ОПЕРАТОР: select1;
Теперь сообщение выводится клиенту и в журнал на русском языке.
А почему осталась одна строка на английском языке?
Эту строку выводит клиент psql. Язык сообщений клиента определяется переменной ОС LC_MESSAGES:
=> \! echo $LC_MESSAGES
en_US.UTF8
Для полной русификации установим русский язык и для сообщений клиента. Мы используем клиент (psql), поставляемый c сервером, поэтому можно не проверять, что клиент собран с поддержкой NLS.
=> \q
student$ export LC_MESSAGES=ru_RU.UTF8
student$ psql
=> SET lc_messages TO 'ru_RU.UTF8';
SET
=> select1;
ОШИБКА: ошибка синтаксиса (примерное положение: "select1") СТРОКА 1: select1; ^
Теперь все сообщения на русском языке.
Конец демонстрации.