Migrating the StormBPMN Database to UTF-8
Migrating the StormBPMN Database to UTF-8
A guide for the database administrator of an on-premise installation. The goal is to convert the StormBPMN database from SQL_ASCII to UTF-8 to ensure that case-insensitive search and deduplication for Cyrillic characters work correctly.
All commands and thresholds have been verified on PostgreSQL 16, database
SQL_ASCII | C | C(matching the target installation). Before proceeding, read the entire document, especially section 3 (data diagnostics).
1. Why
StormBPMN supports databases only in UTF-8 encoding β this is an official requirement of Storm Storm Enterprise (install, get-started): "UTF8 encoding β correct text storage, ICU collation, and natural sorting of names". This instruction aligns the installation on SQL_ASCII with this requirement.
In addition to the official requirement: encoding alone is insufficient β a UTF-8-compatible locale is also required. ICU collation fixes sorting, but case handling (lower(), case-insensitive search) depends on LC_CTYPE. On SQL_ASCII (and on UTF-8 with the C locale), the lower() function does not convert Cyrillic characters to lowercase β case-insensitive search for Russian names/titles (employees, roles, diagrams, departments, comments, etc.) and deduplication during import do not work correctly: searching for "ΠΈΠ²Π°Π½ΠΎΠ²" does not find "ΠΠ²Π°Π½ΠΎΠ²".
| encoding | LC_CTYPE | lower('ΠΠ²Π°Π½ΠΎΠ²') | 'ΠΠ²Π°Π½ΠΎΠ²' ILIKE '%ΠΈΠ²Π°Π½ΠΎΠ²%' |
|---|---|---|
SQL_ASCII | C | ΠΠ²Π°Π½ΠΎΠ² β | false β |
UTF8 | C | ΠΠ²Π°Π½ΠΎΠ² β | false β |
UTF8 | C.UTF-8 | ΠΈΠ²Π°Π½ΠΎΠ² β
| true β
|
UTF8 | ru_RU.UTF-8 / en_US.UTF-8 | ΠΈΠ²Π°Π½ΠΎΠ² β
| true β
|
Important: changing only
encodingis not enough β the locale (LC_CTYPE) must also be UTF-8-compatible (C.UTF-8,ru_RU.UTF-8,en_US.UTF-8), notC.
2. Preparation (pre-flight)
3. Diagnostics (MANDATORY before migration)
3.1. Current encoding/locale
SELECT datname,
pg_encoding_to_char(encoding) AS encoding,
datcollate, datctype
FROM pg_database
WHERE datname = current_database();
-- ΠΠΎΠ½ΡΡΠΎΠ»ΡΠ½Π°Ρ ΠΏΡΠΎΠ±Π°: Π΅ΡΠ»ΠΈ Π²Π΅ΡΠ½ΡΠ»ΠΎΡΡ 'ΠΠ²Π°Π½ΠΎΠ²' (ΡΠ΅Π³ΠΈΡΡΡ Π½Π΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠ»ΡΡ) β ΠΌΠΈΠ³ΡΠ°ΡΠΈΡ Π½ΡΠΆΠ½Π°.
SELECT lower('ΠΠ²Π°Π½ΠΎΠ²');
3.2. Are the data valid UTF-8 β a critical step
SQL_ASCII stores bytes "as is," without validation. The subsequent fate of the migration depends on the encoding in which the Cyrillic characters are actually stored:
- Storm writes data as UTF-8 (the driver uses
client_encoding=UTF8) β the expected case, in which the migration is straightforward (scenario A). - However, if data from legacy sources or manual edits in CP1251 (Windows-1251) or another single-byte encoding ended up in the database, it cannot simply be "renamed" to UTF-8; transcoding is required (scenario B).
Quick indicator (single-byte vs. UTF-8). For UTF-8, Cyrillic characters occupy 2 bytes per character, while for CP1251 they occupy 1 byte. Take a known value and compare its length in bytes:
-- Π΄Π»Ρ 'ΠΠ²Π°Π½ΠΎΠ²' (6 ΡΠΈΠΌΠ²ΠΎΠ»ΠΎΠ²): 12 β UTF-8; 6 β ΠΎΠ΄Π½ΠΎΠ±Π°ΠΉΡΠΎΠ²Π°Ρ (CP1251)
SELECT octet_length(last_name) AS bytes, last_name
FROM sm_persons
WHERE last_name IS NOT NULL
ORDER BY id LIMIT 20;
Reliable check β "test dump to UTF-8". pg_dump --encoding=UTF8 fails on the very first invalid byte, thus serving as a validator for the entire database at once:
pg_dump -U <user> --encoding=UTF8 -Fc -d <db> -f /tmp/storm.utf8.dump
# Π£ΡΠΏΠ΅Ρ
(exit 0) β Π΄Π°Π½Π½ΡΠ΅ β Π²Π°Π»ΠΈΠ΄Π½ΡΠΉ UTF-8, ΠΈΠ΄ΡΠΌ ΠΏΠΎ ΡΡΠ΅Π½Π°ΡΠΈΡ A (ΡΡΠΎΡ ΠΆΠ΅ Π΄Π°ΠΌΠΏ ΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ).
# ΠΡΠΈΠ±ΠΊΠ° Π²ΠΈΠ΄Π°:
# pg_dump: detail: invalid byte sequence for encoding "UTF8": 0xc8 0xe2
# β Π΅ΡΡΡ Β«Π±ΠΈΡΡΠ΅Β»/ΠΎΠ΄Π½ΠΎΠ±Π°ΠΉΡΠΎΠ²ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅, ΡΡΠ΅Π½Π°ΡΠΈΠΉ B.
List of specific corrupted strings (if targeted cleanup is needed) β for each text column, for example:
DO $$
DECLARE r record; bad int[] := '{}';
BEGIN
FOR r IN SELECT id, last_name AS v FROM sm_persons LOOP
BEGIN PERFORM convert_from(r.v::bytea, 'UTF8');
EXCEPTION WHEN others THEN bad := bad || r.id; END;
END LOOP;
RAISE NOTICE 'sm_persons Ρ Π±ΠΈΡΡΠΌΠΈ Π±Π°ΠΉΡΠ°ΠΌΠΈ (id): %', bad;
END $$;
β οΈ Dangerous nuance. A standard
pg_dump(without--encoding=UTF8) +pg_restorewill silently carry over corrupted bytes to the new UTF-8 database (restore inheritsclient_encoding=SQL_ASCIIfrom the dump and does not validate). The database will be "UTF-8," but it will contain invalid byte mines inside. Therefore, always use--encoding=UTF8for dumps β it prevents dirty data from migrating unnoticed.
4. Migration (with downtime)
The encoding of the existing database cannot be changed in place (ALTER DATABASE ... ENCODING does not exist) β only dump β new UTF-8 database β restore.
# 0. ΠΡΠΊΠ°ΠΏ ΡΠΆΠ΅ ΡΠ΄Π΅Π»Π°Π½ (ΡΠ°Π·Π΄Π΅Π» 2).
# 1. ΠΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ (Storm backend), ΡΡΠΎΠ±Ρ Π½Π΅ Π±ΡΠ»ΠΎ Π·Π°ΠΏΠΈΡΠΈ.
# 2. ΠΠ°ΠΌΠΏ + Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΡ ΠΎΠ΄Π½ΠΈΠΌ ΡΠ°Π³ΠΎΠΌ (ΡΠΌ. 3.2). ΠΡΠ»ΠΈ ΡΠΏΠ°Π» β ΡΡΠ΅Π½Π°ΡΠΈΠΉ B, ΡΠΌ. Π½ΠΈΠΆΠ΅.
pg_dump -U <user> --encoding=UTF8 -Fc -d <db> -f /tmp/storm.utf8.dump
# 3. Π‘ΠΎΠ·Π΄Π°ΡΡ Π½ΠΎΠ²ΡΡ UTF-8 ΠΠ Ρ UTF-8-Π»ΠΎΠΊΠ°Π»ΡΡ (ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΠΎ ΠΈΠ· template0).
createdb -U <user> --encoding=UTF8 \
--lc-ctype=ru_RU.UTF-8 --lc-collate=ru_RU.UTF-8 \
--template=template0 storm_utf8
# (Π½Π΅Ρ ru_RU.UTF-8 β ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ C.UTF-8)
# ΠΠ»ΡΡΠ΅ΡΠ½Π°ΡΠΈΠ²Π° β Π½ΠΎΠ²ΡΠΉ ΠΊΠ»Π°ΡΡΠ΅Ρ: initdb --encoding=UTF8 --locale=ru_RU.UTF-8 -D <newdir>
# 4. Restore. PGCLIENTENCODING=UTF8 Π³Π°ΡΠ°Π½ΡΠΈΡΡΠ΅Ρ Π²Π°Π»ΠΈΠ΄Π°ΡΠΈΡ Π½Π° Π²ΡΡΠ°Π²ΠΊΠ΅.
PGCLIENTENCODING=UTF8 pg_restore -U <user> -d storm_utf8 -j 4 /tmp/storm.utf8.dump
# -j 4 β ΠΏΠ°ΡΠ°Π»Π»Π΅Π»ΡΠ½ΡΠ΅ Π²ΠΎΡΠΊΠ΅ΡΡ (ΡΡΠΊΠΎΡΡΠ΅Ρ; ΠΏΠΎΠ΄Π±Π΅ΡΠΈΡΠ΅ ΠΏΠΎ ΡΠΈΡΠ»Ρ ΡΠ΄Π΅Ρ).
# ΠΡΠΈ ΠΏΡΠΎΠ±Π»Π΅ΠΌΠ°Ρ
Ρ Π²Π»Π°Π΄Π΅Π»ΡΡΠ°ΠΌΠΈ/ΡΠΎΠ»ΡΠΌΠΈ: Π΄ΠΎΠ±Π°Π²ΡΡΠ΅ --no-owner --role=<user>.
# 5. ΠΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΡΠ΅ ΡΠ°ΡΡΠΈΡΠ΅Π½ΠΈΡ. pg_dump ΠΏΠ΅ΡΠ΅Π½ΠΎΡΠΈΡ ΠΈΡ
ΡΠ°ΠΌ, Π½ΠΎ ΠΏΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ ΡΠ²Π½ΠΎ
# (Storm ΡΡΠ΅Π±ΡΠ΅Ρ ΡΠΎΠ·Π΄Π°Π²Π°ΡΡ pg_trgm Π²ΡΡΡΠ½ΡΡ, ΠΈΠ½Π°ΡΠ΅ Π°Π²ΡΠΎΠΊΠΎΠΌΠΏΠ»ΠΈΡ ΠΌΠΎΠ»ΡΠ° Π½Π΅ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ):
psql -U <user> -d storm_utf8 <<'EOSQL'
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS hstore;
EOSQL
# 6. ΠΠ΅ΡΠ΅ΠΊΠ»ΡΡΠΈΡΡ datasource ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ Π½Π° storm_utf8 ΠΈ Π·Π°ΠΏΡΡΡΠΈΡΡ backend.
Scenario B β data in CP1251 (if step 2 failed)
Transcoding is required. It is safer to do this on a text dump via iconv (ASCII parts are invariant):
pg_dump -U <user> -Fp -d <db> -f /tmp/storm.plain.sql # plain-text Π΄Π°ΠΌΠΏ (ΠΊΠ°ΠΊ Π΅ΡΡΡ)
iconv -f WINDOWS-1251 -t UTF-8 /tmp/storm.plain.sql -o /tmp/storm.win1251-to-utf8.sql
# Π£Π±Π΅Π΄ΠΈΡΡΡΡ, ΡΡΠΎ Π² ΡΠ°ΠΏΠΊΠ΅ Π΄Π°ΠΌΠΏΠ° client_encoding = UTF8 (ΠΏΠΎΠΏΡΠ°Π²ΠΈΡΡ ΠΏΡΠΈ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎΡΡΠΈ),
# Π·Π°ΡΠ΅ΠΌ Π²ΠΎΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ Π² UTF-8 ΠΠ ΠΈΠ· ΡΠ°Π·Π΄Π΅Π»Π° 4 ΡΠ°Π³ 3:
PGCLIENTENCODING=UTF8 psql -U <user> -d storm_utf8 -f /tmp/storm.win1251-to-utf8.sql
If the database mixes UTF-8 and CP1251 (a common result of a long life with SQL_ASCII) β a single
iconvis not enough; such cases are handled by the DBA line by line (based on the list from 3.2). Do not migrate "blindly".
5. Post-migration verification
\c storm_utf8
-- ΠΊΠΎΠ΄ΠΈΡΠΎΠ²ΠΊΠ°/Π»ΠΎΠΊΠ°Π»Ρ
SELECT pg_encoding_to_char(encoding), datcollate, datctype
FROM pg_database WHERE datname = current_database(); -- ΠΎΠΆΠΈΠ΄Π°Π΅ΠΌ UTF8 + *.UTF-8
-- ΡΠΎΠ»Π΄ΠΈΠ½Π³ ΠΊΠΈΡΠΈΠ»Π»ΠΈΡΡ
SELECT lower('ΠΠ²Π°Π½ΠΎΠ²'); -- ΠΎΠΆΠΈΠ΄Π°Π΅ΠΌ 'ΠΈΠ²Π°Π½ΠΎΠ²'
-- Π΄Π°Π½Π½ΡΠ΅ Π½Π° ΠΌΠ΅ΡΡΠ΅ ΠΈ Π½Π΅ Β«ΠΊΡΠ°ΠΊΠΎΠ·ΡΠ±ΡΡΒ»
SELECT count(*) FROM sm_persons; -- ΡΠ²Π΅ΡΠΈΡΡ Ρ ΠΈΡΡΠΎΡΠ½ΠΈΠΊΠΎΠΌ
SELECT id, last_name, first_name FROM sm_persons ORDER BY id LIMIT 10;
-- ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΡΠ΅ ΡΠ°ΡΡΠΈΡΠ΅Π½ΠΈΡ Π½Π° ΠΌΠ΅ΡΡΠ΅ (ΠΎΠΆΠΈΠ΄Π°Π΅ΠΌ 5 ΡΡΡΠΎΠΊ)
SELECT extname FROM pg_extension
WHERE extname IN ('vector','pg_trgm','pgcrypto','uuid-ossp','hstore') ORDER BY extname;
6. Rollback
- Do not delete the old database until the new one has been verified and the application has run normally.
- Rollback = revert the application's datasource to the old database and restart the backend.
7. Notes
--template=template0is required when changing the locale relative totemplate1.- After switching to UTF-8, ICU-collation
natural_sortwill be applied (changeset DEV-871 intentionally skipped it for non-UTF8) β "human" sorting of numbers in names. - Extensions/roles/sequences
pg_dump -Fc/pg_restoreare migrated automatically, but the receiver binary must contain the packages (hence the imagepgvector/pgvector:pg17);pg_trgmStorm requires explicit creation (step 4, item 5). In case of owner mismatch β--no-owner --role=<user>, create roles in advance.
8. If migration is not performed
Case-insensitive search and deduplication for Cyrillic characters on SQL_ASCII will not work β this is a limitation of the specific installation, not a defect of Stormbpmn. Workarounds on the application side for unsupported encodings are not provided.