콘텐츠로 이동

(KO) CUBRID 문자셋과 콜레이션 — 코드셋 변환, 로케일 인지 비교, 다중 인코딩

CUBRID 문자셋과 콜레이션 — 코드셋 변환, 로케일 인지 비교, 다중 인코딩

섹션 제목: “CUBRID 문자셋과 콜레이션 — 코드셋 변환, 로케일 인지 비교, 다중 인코딩”

CUBRID은 텍스트를 (바이트 시퀀스, 코드셋, 콜레이션) 의 세 쌍으로 다룬다. 코드셋 (INTL_CODESET) 은 엔진에게 바이트를 어떻게 문자 단위로 걸어 갈지를 알려 주고, 콜레이션 (LANG_COLLATION) 은 그 문자들을 어떻게 정렬·매치·해시할지를 알려 준다. 두 축은 분리되어 있지만 묶여 있다 — 모든 콜레이션은 정확히 한 코드셋에 자기 자신을 고정해 두며, 문자열의 코드셋은 비교가 시작되기 전에 콜레이션의 코드셋으로 강제 변환 가능해야만 한다.

이 모듈은 다른 RDBMS 엔진과 비교했을 때 두 가지 점에서 특이하다. 첫째, CUBRID은 ICU 에 링크하지 않는다. LDML 로 작성된 UCA tailoring 을 포함한 로케일 규칙은 CUBRID 자체의 genlocale 도구체인이 플랫폼별 공유 라이브러리 (libcubrid_<lang>.so/.dll) 로 컴파일하고, 서버가 시작 시점에 이를 dlopen 한다. 둘째, 모든 코드셋이 자신만의 비교기 가족 (lang_fastcmp_byte, lang_strcmp_utf8, lang_strcmp_utf8_uca, …) 이 있고, LANG_COLLATION vtable 에 와이어링되어 있다. 그래서 hot path 에서는 변환 루틴이 경계에서 지불하는 코드셋 dispatch switch 를 건너뛴다.

목차

심볼파일라인
enum intl_codesetsrc/base/intl_support.h175
INTL_CODESET_MULTsrc/base/intl_support.h77
INTL_NEXT_CHARsrc/base/intl_support.h99
intl_Len_utf8_char (decl)src/base/intl_support.h173
intl_utf8_to_cpsrc/base/intl_support.c3754
intl_cp_to_utf8src/base/intl_support.c3661
intl_back_utf8_to_cpsrc/base/intl_support.c3812
intl_check_utf8src/base/intl_support.c3950
intl_check_euckrsrc/base/intl_support.c4239
intl_fast_iso88591_to_utf8src/base/intl_support.c4932
intl_euckr_to_utf8src/base/intl_support.c5101
intl_utf8_to_euckrsrc/base/intl_support.c5256
intl_convert_charsetsrc/base/intl_support.c944
intl_char_count / intl_char_sizesrc/base/intl_support.c974 / 1021
intl_identifier_casecmpsrc/base/intl_support.c2783
struct lang_collationsrc/base/language_support.h159
struct lang_locale_datasrc/base/language_support.h190
struct db_charset (lang_Db_charsets)src/base/language_support.h:128 / .c:171
enum LANG_COLL_* (built-in IDs)src/base/language_support.h105
LANG_GET_BINARY_COLLATIONsrc/base/language_support.h121
LANG_RT_COMMON_COLLsrc/base/language_support.h65
built_In_collations[]src/base/language_support.c830
lang_init_builtinsrc/base/language_support.c850
register_collationsrc/base/language_support.c1568
lang_get_collationsrc/base/language_support.c1648
lang_get_collation_by_namesrc/base/language_support.c1678
lang_load_coll_from_libsrc/base/language_support.c7105
lang_load_librarysrc/base/language_support.c7272
lang_fastcmp_bytesrc/base/language_support.c5453
lang_fastcmp_binarysrc/base/language_support.c6415
lang_strcmp_utf8src/base/language_support.c2807
lang_strmatch_utf8src/base/language_support.c2830
lang_strcmp_utf8_ucasrc/base/language_support.c4321
lang_strmatch_utf8_uca_w_coll_datasrc/base/language_support.c4368
lang_strmatch_utf8_uca_w_levelsrc/base/language_support.c3583
lang_get_uca_w_l13src/base/language_support.c3376
lang_get_contr_for_stringsrc/base/language_support.c3296
lang_init_coll_en_cs / _en_cisrc/base/language_support.c5304 / 5346
struct coll_datasrc/base/locale_support.h354
struct coll_tailoringsrc/base/locale_support.h391
struct uca_optionssrc/base/locale_support.h315
struct alphabet_datasrc/base/locale_support.h437
struct text_conversionsrc/base/locale_support.h464
struct unicode_normalizationsrc/base/locale_support.h492
locale_compile_localesrc/base/locale_support.c4573
COLL_CONTRACTIONsrc/base/locale_lib_common.h44
QSTR_COMPARE macrosrc/query/string_opfunc.h59
QSTR_MATCH macrosrc/query/string_opfunc.h62
QSTR_NEXT_ALPHA_CHAR macrosrc/query/string_opfunc.h68
QSTR_SPLIT_KEY macrosrc/query/string_opfunc.h71
btree_compare_individual_key_valuesrc/storage/btree.c19602

텍스트를 다루는 DBMS 는 직교하는 두 질문에 답해야만 한다. 인코딩 은 바이트 시퀀스를 문자로 디코드하고, 콜레이션 은 이미 디코드된 문자를 정렬한다. 원리상 두 축은 독립이지만 실제 구현에서는 묶여 있다 — 모든 비교기는 바이트 스트림을 걸어가야 하고, 그 걷는 방식은 인코딩이 좌우한다.

코드포인트는 U+0000 .. U+10FFFF 범위의 추상 정수다. UTF-8 은 이를 1~4 바이트로 직렬화한다 — ASCII 호환 (< 0x80), 자기 동기화 (continuation 바이트가 10xxxxxx 로 시작), lead 바이트에 길이가 인코드 (110xxxxx = 2 바이트, 1110xxxx = 3, 11110xxx = 4). 그리고 Unicode 이전의 인코딩들 — ISO-8859-1, EUC-KR, Shift-JIS — 이 레거시 데이터와 CSV import 에 남아 있어서, 어떤 RDBMS 도 이들을 받아들일 수 있어야 한다.

Unicode Collation Algorithm (UCA, UTS #10) 은 각 코드포인트에 collation element (CE) 를 부여한다 — 세 개 또는 네 개 레벨의 가중치 튜플이다. L1 = base letter, L2 = accent, L3 = case, L4 = punctuation/variable. 정렬은 L1 키를 비교하고, 동률이면 L2, 그렇게 내려간다. 두 가지 확장이 중요하다. expansion (예 — ß → “ss) 과 contraction (예 — 스페인어에서 ch 가 한 단위로 정렬). 기준선이 되는 표는 Default Unicode Collation Element Table (DUCET) 이고, 로케일별 tailoring 이 그것을 덮어쓴다. tailoring 은 LDML (CLDR XML) 로 작성되며 & a < b (primary 레벨에서 b 가 a 다음으로 정렬”) 같은 규칙을 쓴다.

대소문자 구분 (CI/CS), 강세 구분 (AI/AS), kana 구분 (KI/KS) 은 보통 레벨 1 에서 비교를 잘라내거나 (CI/AI), 동치인 CE 들을 공유 가중치로 접어서 구현한다.

대소문자 변환은 로케일에 의존한다. 교과서적인 예가 터키어다 — LOWER('I')'ı' (U+0131) 이고 UPPER('i')'İ' (U+0130) 다. 즉 엔진이 C 런타임의 tolower/toupper 를 그대로 쓸 수 없다는 뜻이다.

정규화 가 그 밑바닥에 깔려 있다. é 는 U+00E9 (NFC) 이거나 U+0065+U+0301 (NFD) 이다. UCA 표는 NFC 를 가정한다. 입력 시 정규화하지 않는 엔진은 결국 의미상 동치인 두 문자열이 비교 시 같지 않게 되는 결과를 마주한다.

구현체는 보통 세 가지 패턴 중 하나를 따른다.

  • ICU 에 링크 — 변환·반복·정규화·콜레이션을 통째로 위임한다.
  • CLDR 데이터 위에서 자체 UCA 를 굴린다 — 빌드 시점에 DUCET 와 LDML 을 파싱해 압축된 표로 emit 한다.
  • 단순함 우선 — 바이너리 콜레이션과 언어별 손튜닝 표 몇 장으로 끝낸다.

CUBRID 은 두 번째 패턴에 산다. genlocale 이 LDML 을 파싱하고, DUCET 위에 tailoring 을 적용해 로케일별 C 소스를 emit 하면, CMake 가 이를 공유 라이브러리로 빌드하고, 런타임은 그것을 dlopen 한다. ICU 는 의존성이 아니다.

  • PostgreSQL 은 원래 libc 의 strcoll 을 썼다. 10 부터 ICU 가 1 급 provider 가 되었고, 16 부터는 그것이 기본값이다. pg_collation(collname, collprovider, collcollate, collctype, collicurules) 를 보관한다. 인코딩은 데이터베이스 단위다. 콜레이션은 type 의 일부로 흘러 다닌다 — 연산자가 동의해야 하고, 충돌 시 “could not determine which collation to use” 가 발생한다.
  • MySQL/MariaDB 는 ICU 이전부터 자기 프레임워크가 있었다. 약 40 개의 charset, 각각 여러 콜레이션 (utf8mb4_general_ci, utf8mb4_0900_ai_ci). 각 콜레이션이 함수 포인터 (strnncoll, hash_sort, like_range) 를 들고 있는 CHARSET_INFO 구조체다. CUBRID 의 LANG_COLLATION 과 거의 쌍둥이다. MySQL 8.0 은 CLDR 기반 UCA 9.0 콜레이션을 ship 한다.
  • Oracle 은 NLS 를 쓴다. 데이터베이스 캐릭터 셋은 CREATE DATABASE 시점에 고정되고, 언어학적 콜레이션은 NLS_SORT (BINARY, XSPANISH, …) 와 _CI/_AI 접미사로 표현된다. 자체 콜레이션 엔진이 있고, ICU 보다 오래됐다.
  • SQL Server 는 UTF-8 콜레이션 (Latin1_General_100_CI_AS_SC_UTF8) 을 2019 에 와서야 추가했다. 콜레이션 이름이 로케일·버전·CI/CS·AI/AS·KI/KS·WI/WS·supplementary 문자·UTF-8 을 모두 한 문자열로 인코드한다.
  • SQLite 는 빌트인 세 개 (BINARY, ASCII 전용 NOCASE, RTRIM) 와 sqlite3_create_collation 확장 API 가 있다.

CUBRID 의 모양은 MySQL 에 가장 가깝다. 작은 고정 charset 집합 (4 개 — binary, ISO-8859-1, EUC-KR, UTF-8 — lang_Db_charsets[]), 빌트인 콜레이션 ID 가 0..31, 사용자 콜레이션이 32..255, 모두 LANG_COLLATION vtable 으로 dispatch 된다. 차별화는 빌드 타임 파이프라인 — 로케일 데이터가 런타임 파일로 로드되지 않고, 로케일별 공유 라이브러리로 컴파일되어 서버가 dlopen 한다는 점이다.

이론적 개념CUBRID 명칭
인코딩 enumenum intl_codeset (intl_support.h:175)
codepoint 변환기intl_utf8_to_cp / intl_cp_to_utf8 (intl_support.c)
인코딩 검증기intl_check_utf8 / intl_check_euckr (intl_support.c)
콜레이션 vtablestruct lang_collation (language_support.h:159)
UCA 가중치 표struct coll_data::uca_w_l13 (locale_support.h:354)
LDML tailoringstruct coll_tailoring (locale_support.h:391)
로케일 데이터 묶음struct lang_locale_data (language_support.h:190)
대소문자 표struct alphabet_data (locale_support.h:437)
콘솔 코드페이지 변환기struct text_conversion (locale_support.h:464)
정규화 표struct unicode_normalization (locale_support.h:492)
빌드 타임 컴파일러genlocale 바이너리 → locale_compile_locale (locale_support.c:4573)
런타임 dlopen 진입lang_load_library (language_support.c:7272)
빌트인 식별자LANG_COLL_* enum (language_support.h:105)
Hot path 비교기lang_fastcmp_byte (language_support.c:5453)
codepoint 비교기lang_strcmp_utf8 (language_support.c:2807)
UCA 비교기lang_strcmp_utf8_uca (language_support.c:4321)
강제 변환 (coercion)LANG_RT_COMMON_COLL 매크로 (language_support.h:65)
호출 자리 매크로QSTR_COMPARE / QSTR_MATCH / QSTR_SPLIT_KEY (string_opfunc.h:59-71)
B+Tree 소비자btree_compare_individual_key_value (btree.c:19602)

모델은 여섯 개의 구조체로 정리된다. INTL_CODESET (인코딩의 닫힌 enum), LANG_COLLATION (콜레이션별 vtable), COLL_DATA (가중치 표와 UCA 옵션), LANG_LOCALE_DATA (언어-코드셋별 알파벳·달력·통화), ALPHABET_DATA (코드포인트 → 소문자/대문자 with multiplier), TEXT_CONVERSION (콘솔 코드페이지 ↔ UTF-8).

빌트인 콜레이션은 language_support.c:696..791 의 정적 전역으로 선언되고, 830 번 줄의 built_In_collations[] 에 모인다. 사용자 콜레이션은 lang_load_coll_from_lib (7105 줄) 가 로드한다. 같은 lang_Collations[256] 배열이 둘 다 들고 있다 — 0..31 은 빌트인용, 32..255 는 사용자 정의용으로 예약되어 있다.

ICU 를 쓰지 않기로 한 결정 뒤에는 코드에서 모두 보이는 세 가지 제약이 있다.

  • 단일 바이너리 ship — cub_servercubridsa 는 ICU 런타임 설치를 요구하지 않아야 한다.
  • 결정론적 업그레이드 — ICU 콜레이션 표는 버전 (9.0/10.0/13.0) 사이에서 흔들린다. 표를 freeze 하고 콜레이션별로 MD5 체크섬 (coll_data.checksum[33], lang_check_coll_compat 가 검증) 을 두면, 인덱스가 업그레이드를 살아남는다.
  • 코드셋 직접 비교 — 서버가 EUC-KR 위에서 동작할 수 있다. B+Tree 는 EUC-KR 바이트를 변환 hop 없이 직접 비교해야만 한다. 그래서 각 코드셋이 자기 비교기를 vtable 으로 와이어링받는다.

enum intl_codeset (intl_support.h:175) 은 여섯 개 값에 에러/none 센티넬을 더해 정의한다 — ASCII = 0, RAW_BITS = 1 (BIT 타입), RAW_BYTES = 2 (BINARY, BINARY 로도 alias 됨), ISO88591 = 3, KSC5601_EUC = 4, UTF8 = 5. INTL_CODESET_LAST = UTF8.

코드셋 인지 코드를 굴리는 매크로 두 개가 있다. INTL_CODESET_MULT(cs) 는 문자당 최대 바이트 수를 반환한다 — UTF-8 은 INTL_UTF8_MAX_CHAR_SIZE (4), EUC-KR 은 3 (KSC 5601 의 두 바이트 형식과 JIS X 0212 의 세 바이트 형식), 나머지는 1. INTL_NEXT_CHAR(ptr, s, codeset, *char_size) 는 한 문자씩 전진시킨다 — UTF-8 은 intl_nextchar_utf8 로, EUC-KR 은 intl_nextchar_euc 로 dispatch 하고, 그 외엔 s + 1.

intl_Len_utf8_char[256] (intl_support.h:173) 은 분기 없는 UTF-8 walk 을 가능하게 하는 표다. lead 바이트로 인덱스해서 바이트 길이를 얻는다 (잘못된 lead 면 1, best-effort 회복). 매크로 INTL_NEXTCHAR_UTF8(c)c + intl_Len_utf8_char[*c] 다.

DB 가 노출하는 charset 표는 lang_Db_charsets[] (language_support.c:171) 로, (charset_name, charset_desc, space_char, introducer, charset_cubrid_name, charset_id, space_size) 의 여섯 행이다.

nameintroducerspace (pad)idspace_size
ascii ASCII1
raw-bits""RAW_BITS1
raw-bytes_binary""BINARY1
iso8859-1_iso88591 ISO885911
ksc-euc_euckr"\241\241" (U+3000 ideographic)KSC5601_EUC2
utf-8_utf8 UTF81

space_char 가 CHAR 의 고정 길이 패딩이다. EUC-KR 은 U+3000 (ideographic space, 두 바이트) 을 쓰고, 나머지는 ASCII SPACE 를 쓴다. introducer 는 문법이 _utf8'literal' 형태로 받아들이는 표식이다.

forward·backward·emit 을 담당하는 세 개의 atomic 함수가 있다.

  • intl_cp_to_utf8 (3661 줄) — codepoint → 1..4 바이트 UTF-8 시퀀스. codepoint 범위 (≤ 0x7F, ≤ 0x7FF, ≤ 0xFFFF, ≤ 0x10FFFF) 로 분기. assert 실패 시 '?' 와 1 을 반환.
  • intl_utf8_to_cp (3754 줄) — UTF-8 → codepoint. lead 바이트 상위 비트로 dispatch 하고, malformed 시퀀스에서는 0xFFFFFFFF 를 반환하면서 *next_char 를 무조건 최소 1 만큼은 전진시킨다 — caller 가 무한 루프에 빠지지 않게 하기 위함이다. 사전 검증된 입력에 대한 비용 — 분기 한 번과 2~4 바이트 로드.
  • intl_back_utf8_to_cp (3812 줄) — utf8_last 부터 거꾸로 walk. continuation 바이트 (b & 0xc0) == 0x80 를 건너뛴 다음 다시 forward 로 인코드. UCA backward 정렬 (프랑스어 L2 reverse 규칙) 과 LIKE escape lookback 에서 사용.

이미 검증된 스트림에서는 표 기반 INTL_NEXTCHAR_UTF8 매크로 (intl_support.h:67) 가 intl_utf8_to_cp 보다 빠르다 — intl_Len_utf8_char[lead_byte] 가 바이트 길이를 돌려준다. UCA inner loop 안에서 사용된다 (입력 검증은 이미 끝났기 때문이다).

intl_check_utf8 (intl_support.c:3950) 가 게이트키퍼다. 외부 (parser, CSQL 입력, client-server 프로토콜) 에서 문자열이 시스템에 들어올 때마다 호출된다. 현대 UTF-8 문법 — 정확히 RFC 3629 — 을 인코드하고, overlong 형식 (0xC0..0xC1), surrogate (0xED 0xA0..0xBF), 범위 밖 4 바이트 시퀀스 (0xF4 0x90..0xBF 이상) 를 거부한다.

Valid ranges enforced by intl_check_utf8:
1 byte : 00 - 7F
2 bytes: C2 - DF , 80 - BF (U+80 .. U+7FF)
3 bytes: E0 , A0 - BF , 80 - BF (U+800 .. U+FFF)
E1 - EC , 80 - BF , 80 - BF (U+1000 .. U+CFFF)
ED , 80 - 9F , 80 - BF (U+D000 .. U+D7FF) — excludes surrogates
EE - EF , 80 - BF , 80 - BF (U+E000 .. U+FFFF)
4 bytes: F0 , 90 - BF , 80 - BF , 80 - BF (U+10000 .. U+3FFFF)
F1 - F3 , 80 - BF , 80 - BF , 80 - BF (U+40000 .. U+FFFFF)
F4 , 80 - 8F , 80 - BF , 80 - BF (U+100000 .. U+10FFFF)

반환은 tristate 다 — INTL_UTF8_VALID, INTL_UTF8_INVALID (잘못된 바이트 시퀀스, *pos 가 시작 지점을 가리킴), INTL_UTF8_TRUNCATED (문자 중간에 끝남, TEXT 컬럼이 바이너리로 잘렸을 때 흔함).

EUC-KR 은 자체 검증기 intl_check_euckr (4239 줄) 을 가진다. EUC-KR 은 세 가지 family 를 허용한다 — ASCII (0x00..0x7F), KSC 5601 (0xA1..0xFE lead, 0xA1..0xFE trail, 두 바이트), JIS X 0212 (0x8F lead, 두 개의 0xA1..0xFE continuation 바이트, 총 세 바이트). 그래서 EUC-KR 의 INTL_CODESET_MULT 가 2 가 아니라 3 인 것이다.

flowchart LR
  subgraph Input
    A1[ISO-8859-1 바이트] --> C1[intl_iso88591_to_utf8]
    A2[EUC-KR 바이트] --> C2[intl_euckr_to_utf8]
    A3[콘솔 MBCS] --> C3[TEXT_CONVERSION.text_to_utf8]
    A4[UTF-8 바이트] --> V[intl_check_utf8]
  end
  C1 --> U[UTF-8 스트림]
  C2 --> U
  C3 --> U
  V --> U
  U --> CP[intl_utf8_to_cp]
  CP --> CMP[콜레이션 비교기]
  CP --> NORM[unicode_compose_string NFC]
  CP --> CASE[intl_upper_string / intl_lower_string]
  U --> O1[intl_utf8_to_iso88591]
  U --> O2[intl_utf8_to_euckr]
  O1 --> Z1[ISO-8859-1 출력]
  O2 --> Z2[EUC-KR 출력]
  U --> Z3[UTF-8 출력]

일반적인 n-to-n 변환기는 없다. 대신 CUBRID 은 쌍쌍 (pairwise) 함수의 삼각형을 제공한다.

SourceTargetFunctionFile:Line
ISO-8859-1UTF-8intl_fast_iso88591_to_utf8intl_support.c:4933
ISO-8859-1EUC-KRintl_iso88591_to_euckr(similar)
EUC-KRISO-8859-1intl_euckr_to_iso88591intl_support.c:4982
EUC-KRUTF-8intl_euckr_to_utf8intl_support.c:5102
UTF-8ISO-8859-1intl_utf8_to_iso88591intl_support.c
UTF-8EUC-KRintl_utf8_to_euckrintl_support.c:5257

ISO-8859-1 → UTF-8 경로는 한 바이트씩 스캔하면서 세 케이스로 갈린다 — ASCII (복사), C1 컨트롤 0x80..0x9F ('?' 로 치환 — CUBRID 은 여기서 Windows-1252 typography 를 해석하지 않는다), Latin-1 0xA0..0xFF (2 바이트 UTF-8 시퀀스 0xC0 | (b >> 6), 0x80 | (b & 0x3F) 로 인코드).

EUC-KR ↔ UTF-8 경로는 ksc5601_mbtowc (선언은 src/base/ksc5601.h, 변환표는 libiconv 로부터 charset_converters.h 로 들어옴) 로 들어간다. 각 0xA1..0xFE lead 바이트마다 두 바이트 KSC 5601 문자를 소비한다 — ksc_buf[0] = lead - 0x80; ksc_buf[1] = trail - 0x80; 그리고 ksc5601_mbtowc → unicode_cp → intl_cp_to_utf8. 0x8F lead 바이트는 jisx0212_mbtowc 를 통한 세 바이트 JIS X 0212 경로를 트리거한다.

charset_converters.h 는 GNU libiconv 에서 들고 온 44 줄짜리 LGPL 헤더다. ksc5601/jisx0212 표가 인덱싱하는 Summary16 두 단계 표 형식 (indx, used 비트마스크) 을 정의한다.

intl_convert_charset (944 줄) 은 ER_QSTR_BAD_SRC_CODESET 을 돌려주는 no-op stub 이다 — 사용자 레벨 CONVERT(s USING ...) 는 와이어링되어 있지 않다. pairwise intl_*_to_* 함수는 type coercion 레이어 안에서 명시적 cast 나 COLLATE 가 transcoding 을 강제할 때만 호출된다.

struct lang_collation (language_support.h:159) 이 들고 다니는 것 — codeset, built_in 플래그, need_init 플래그, COLL_OPT options (allow_like_rewrite, allow_index_opt, allow_prefix_index), LANG_LOCALE_DATA *default_lang 역참조, embedded COLL_DATA coll (가중치, contraction, UCA 옵션), 그리고 함수 포인터 여섯 개.

  • fastcmp(coll, s1, sz1, s2, sz2, ignore_trailing_space) — 순수 문자열 비교.
  • strmatch(coll, is_match, s1, sz1, s2, sz2, escape, has_last_escape, *match_size, ti)LIKE 를 위한 패턴 인지 비교.
  • next_coll_seq(coll, seq, size, next_seq, *len_next, ti)seq 보다 strict 하게 큰 가장 작은 키를 만들어 낸다. range scan 경계 증가에 사용.
  • split_key(coll, is_desc, s1, sz1, s2, sz2, **key, *byte_size, ti)str1 <= str2 일 때 str1 <= K < str2 를 만족하는 가장 짧은 separator 키 K 를 돌려준다. B+Tree 가 페이지 split 시점에 사용.
  • mht2str(coll, str, size) — in-memory hash join 을 위한 정렬-안정 해시. 동등성은 콜레이션과 일치해야만 한다 — CI 해시는 raw 바이트가 아닌 가중치를 walk 해야만 한다.
  • init_coll(coll) — 지연 초기화기. register_collation 이 한 번 호출.

vtable 은 코드셋 × 콜레이션 종류의 cross-product 를 쌍별로 한 개의 구체적 함수 포인터로 해소하는 자리다. 빌트인 콜레이션 열 개의 와이어링은 다음과 같다.

IDNameCodesetfastcmpstrmatchsplit_keymht2str
0iso88591_binISO-8859-1lang_fastcmp_bytelang_strmatch_bytelang_split_key_isolang_mht2str_default
1utf8_binUTF-8lang_fastcmp_bytelang_strmatch_utf8lang_split_key_utf8lang_mht2str_byte
2iso88591_en_csISO-8859-1lang_fastcmp_bytelang_strmatch_bytelang_split_key_bytelang_mht2str_byte
3iso88591_en_ciISO-8859-1lang_fastcmp_bytelang_strmatch_bytelang_split_key_bytelang_mht2str_byte
4utf8_en_csUTF-8lang_fastcmp_bytelang_strmatch_utf8lang_split_key_utf8lang_mht2str_byte
5utf8_en_ciUTF-8lang_fastcmp_bytelang_strmatch_utf8lang_split_key_utf8lang_mht2str_byte
6utf8_tr_csUTF-8lang_strcmp_utf8lang_strmatch_utf8lang_split_key_utf8lang_mht2str_utf8
7utf8_ko_csUTF-8lang_strcmp_utf8lang_strmatch_utf8lang_split_key_utf8lang_mht2str_utf8
8euckr_binEUC-KRlang_fastcmp_bytelang_strmatch_bytelang_split_key_euckrlang_mht2str_ko
9binaryRAW_BYTESlang_fastcmp_binarylang_strmatch_binarylang_split_key_binary(default)

표에서 세 가지를 짚어 둘 만하다.

  • utf8_bin 이 UTF-8 인지 비교가 아니라 lang_fastcmp_byte 를 재사용한다. 이는 옳다 — UTF-8 의 설계가 lexicographic 바이트 비교가 codepoint 비교와 일치한다는 것을 보장한다. 바이트 비교기는 빡빡한 inner loop 라서 어떤 UTF-8 walker 보다도 빠르다.
  • utf8_binstrmatchlang_strmatch_byte 가 아니라 lang_strmatch_utf8 다. 매치 (LIKE) 는 한 번에 한 문자씩 전진해야 한다 — 패턴의 _ 와일드카드가 한 바이트 가 아니라 한 문자 를 뜻하기 때문이다. 그래서 비교는 바이트 레벨에 머물 수 있어도 패턴 매칭은 codepoint 를 walk 해야 한다.
  • ISO-8859-1 위의 CI 콜레이션도 여전히 lang_fastcmp_byte 를 쓴다. 대소문자 무시는 비교기가 아니라 가중치 표 를 바꿔서 달성한다. lang_init_coll_en_ci (5346 줄) 가 coll.weights[] 를 채워 'a'..'z''A'..'Z' 와 같은 가중치를 갖게 한다. 그러면 바이트 비교는 그대로 돈다 — 단지 폴딩된 가중치 표를 읽을 뿐이다.

built_In_collations[] (830 줄) 은 coll_id 순으로 열 개의 포인터를 나열한다 — coll_Iso_binary, coll_Utf8_binary, coll_Iso88591_en_cs, coll_Iso88591_en_ci, coll_Utf8_en_cs, coll_Utf8_en_ci, coll_Utf8_tr_cs, coll_Utf8_ko_cs, coll_Euckr_bin, coll_Binary.

lang_init_builtin (850 줄) 은 256 개의 lang_Collations[] 슬롯을 모두 &coll_Iso_binary 로 미리 채우고, 각 빌트인을 register_collation 을 부른 다음, 일곱 개의 LANG_LOCALE_DATA 구조체를 등록한다. register_collation (1568 줄) 은 현재 점유자가 coll_id == LANG_COLL_DEFAULT (0) 일 때만 lang_Collations[id] = coll 을 쓴다 — first-writer-wins 원칙이고, 이후 충돌은 ER_LOC_INIT 를 돌려준다.

두 콜레이션이 가중치 배열을 공유하지만 초기화의 owner 는 한 쪽뿐이다. register_collationcoll->init_coll 이 NULL 이 아닐 때 한 번만 호출한다.

lang_init_coll_en_cs (5304 줄) 은 identity 가중치를 쓴다 — weights[i] = i, next_cp[i] = i + 1. trailing-insensitive 변종은 추가로 weights_ti[32] = 0, next_cp_ti[32] = 1 을 설정해 SPACE 가 0 가중치를 갖게 한다.

lang_init_coll_en_ci (5346 줄) 은 identity 를 상속한 다음 'a'..'z''A'..'Z' 의 가중치로 덮어쓴다 — 소문자 글자를 weights[i] = i - ('a' - 'A'). CI 는 가중치 표 안에서 완전히 구현된다. 비교기는 lang_fastcmp_byte 그대로다. (이 초기화기들이 쓰는 C++ lambda 는 흔치 않다 — CUBRID 의 .c 파일이 C++17 로 컴파일된다는 점을 떠올리면 된다.)

weights_ti / next_cp_ti 가 trailing-insensitive 변종이다. ignore_trailing_space = true 면 (CHAR vs VARCHAR 를 보고 type system 이 설정한다) 'foo ''foo' 가 동등하게 비교된다. 비교기는 표 포인터를 swap 한다 — inner loop 안에 분기는 없다.

language_support.c:5453. binary, ISO-8859-1, EUC-KR-binary, UTF-8-binary 키 위의 모든 바이트 레벨 B+Tree 비교와 모든 문자열 연산자가 소비하는 함수다. 세 단계로 동작한다.

  1. 공통 prefix 루프 — min(size1, size2) 바이트. 각 바이트 — SPACE → ZERO 의 special case, 그렇지 않으면 weight[byte]. c1 - c2 비교. 0 이 아니면 반환.
  2. trailing-sensitive tail (!ignore_trailing_space) — 사이즈가 다르면 size1 - size2 반환.
  3. trailing-insensitive tail — 더 긴 쪽을 walk. 각 가중치를 ZERO 와 비교. 0 이 아닌 첫 차이가 승자.

미묘한 점들이 있다. SPACE 검사는 어느 가중치 표 변종이 로드되었든 동작하도록 루프 안에 있다. weight[*s1] 은 256/352-entry 표에 대한 L1 캐시 lookup 한 번이다. trailing-space tail 은 두 개의 specialized loop 중 하나를 side 별로 고른다 — 분기 예측기가 안정된 target 을 본다.

language_support.c:2830 (2807 의 lang_strcmp_utf8is_match=false 로 위임해 들어옴). codepoint 레벨 결정이 필요한 로케일 콜레이션 (터키어 I/i, 한국어 한글 순서) 에서 사용. 양쪽 모두 intl_utf8_to_cp 로 한 UTF-8 문자씩 walk 한 다음, codepoint 에 가중치를 부여한다.

  • cp < alpha_cnt — 가중치는 weight_ptr[cp] (SPACE 면 ZERO).
  • cp >= alpha_cnt — 가중치는 codepoint 자체. 로케일이 enumerate 하지 않은 codepoint 까지 total order 를 보존하지만, 그 비용으로 그것들을 로케일 순서가 아닌 순수 Unicode 순서에 둔다.

alpha_cnt 는 터키어가 352 (LANG_CHAR_COUNT_TR, 터키어 알파벳과 주변 Latin Extended-A 를 덮을 만큼) 이고 영어가 256 이다.

완전한 UCA — lang_strmatch_utf8_uca_w_coll_data

섹션 제목: “완전한 UCA — lang_strmatch_utf8_uca_w_coll_data”

콜레이션이 expansion (uca_opt.sett_expansions) 을 가지거나 strength 가 primary 이상일 때, CUBRID 은 language_support.c:4368 의 multi-level UCA 비교기로 떨어진다. 구조는 교과서적인 UCA 의 level-by-level, 동률 시 재시작 그대로다.

  1. 레벨 1 (primary) 비교를 lang_strmatch_utf8_uca_w_level (cd, 0, ...) 로. 0 이 아니면 반환.
  2. strength 가 TAILOR_PRIMARY 뿐이면 — sett_caseLevel 일 때 선택적으로 레벨 3 (case) 수행. sett_caseFirst == 1 (upper-first) 이면 부호를 뒤집는다.
  3. 레벨 2 (accent). sett_backwards (프랑스어 L2 reverse) 면 lang_back_strmatch_utf8_uca_w_level 호출, 아니면 forward.
  4. 레벨 3 (case).
  5. 레벨 4 (variable/quaternary, UCA_L4_W 16 비트 가중치) — sett_strength >= TAILOR_QUATERNARY 일 때만.

레벨 사이를 가로질러 cmp_offset 이 흘러 다닌다. 레벨 2 는 byte 0 이 아니라 레벨 1 이 prefix 가 같다고 선언한 offset 에서 재시작해야 한다. 이것이 이 레벨에서 가장 긴 동등 prefix 의미를 옳게 구현한다.

한 레벨 안에서 — codepoint, contraction, expansion

섹션 제목: “한 레벨 안에서 — codepoint, contraction, expansion”

lang_get_uca_w_l13 (3376 줄) 이 한 source 문자를 가중치 배열 포인터로 옮긴다.

  • intl_utf8_to_cp 로 codepoint 디코드.
  • cp < cd->w_count 이고 codepoint 가 contraction starter (cp_first_contr_offset <= cp < cp_first_contr_offset + cp_first_contr_count, 그리고 남은 텍스트가 충분) 이면 lang_get_contr_for_string (3296 줄) 으로 contraction 을 찾는다. hit 이면 *uca_w_l13 = contr->uca_w_l13, *num_ce = contr->uca_num, *str_next = str + contr->size 가 되고, *cp_out 의 high bit INTL_MASK_CONTR 가 set 된다.
  • 그렇지 않으면 codepoint 별 슬롯 — &cd->uca_w_l13[cp * cd->uca_exp_num]cd->uca_num[cp] 만큼의 유효한 CE. 표는 row-major 로, stride 는 uca_exp_num (로케일 전체에서 최대 CE 개수). 공간상 낭비지만 lookup 은 한 번의 곱셈과 한 번의 덧셈이다.
  • w_count 너머의 codepoint 는 단일 max-weight CE (0xFFFFFFFF) 를 돌려줘서 가장 마지막에 정렬되게 한다.

lang_get_contr_for_string 은 두 단계 필터다 — cp_first_contr_array[cp - offset]cp 로 시작하는 첫 contraction 의 인덱스 (또는 -1) 를 돌려주고, 이어서 contr_list (lexicographic 순서) 를 선형으로 walk 하면서 일치 또는 strict gap 까지 memcmp 한다.

LANG_LOCALE_DATA — 콜레이션 너머의 로케일

섹션 제목: “LANG_LOCALE_DATA — 콜레이션 너머의 로케일”

struct lang_locale_data (language_support.h:190) 이 들고 다니는 것은 (언어, 코드셋) 별 데이터로, 정렬과는 무관 하다 — lang_name, lang_id, codeset, 두 개의 ALPHABET_DATA (일반 문자열 casing 과 식별자 casing), default_lang_coll 역참조, TEXT_CONVERSION *txt_conv (콘솔 MBCS ↔ UTF-8), date/time/timestamp 형식 문자열, 달력 이름 (월·요일·AM/PM 과 그 파스 순서), number_decimal_sym, number_group_sym, default_currency_code, UNICODE_NORMALIZATION unicode_norm, MD5 checksum, 그리고 지연 initloc 콜백. is_user_data.so 에서 로드된 경우 true, 빌트인이면 false.

next_lld 사슬이 같은 언어에 다른 코드셋을 갖는 로케일을 잇는다 — lc_Korean_iso88591lc_Korean_utf8lc_Korean_euckr. 세션이 charset 을 swap 해도 ko_KR 을 여전히 로케일로 resolve 할 수 있다.

ALPHABET_DATA (locale_support.h:437) 가 casing 데이터를 들고 있다 — a_type (UNICODE | ASCII | TAILORED), l_count (covered codepoints), lower_multiplier/upper_multiplier (보통 1, expansion 시 ≥ 2), 그리고 row-major 배열 lower_cp/upper_cp. 인덱스는 [cp * multiplier .. +multiplier - 1]. multiplier ≥ 2 케이스는 ß → SS 대문자화 같은 expansion 을 처리한다 — intl_upper_string (1573 줄) 이 codepoint 별로 walk 하며 &upper_cp[cp * upper_multiplier] 를 lookup 하고, 최대 multiplier 개의 출력 codepoint 를 emit (0 이 조기 종료).

로케일당 두 개의 알파벳이 있는 이유는 터키어가 둘을 갈라놓기 때문이다 — SQL 표준은 식별자에 ASCII 전용 case-insensitivity 를 요구한다 (그래서 SELECTselect 는 같은 키워드). 그러나 터키 로케일의 풀 알파벳은 'I''ı' 로 폴딩한다 — SQL 키워드를 깨뜨린다. intl_identifier_casecmp (2783 줄) 은 ident_alphabet 을 쓰고, db_string_loweralphabet 을 쓴다.

locales/make_locale.sh 가 CMake target cubrid_locale_<lang> 을 구동한다.

flowchart TD
  L1[locales/data/ldml/cubrid_xx_XX.xml]
  L2[locales/data/ducet.txt]
  L3[locales/data/unicodedata.txt]
  L4[locales/data/codepages/CP949.TXT 등]
  G[genlocale 바이너리]
  C1[loclib_*/cubrid_xx_XX.c]
  C2[libcubrid_xx_XX.so]
  S[CUBRID 서버 런타임]
  L1 --> G
  L2 --> G
  L3 --> G
  L4 --> G
  G --> C1
  C1 --> C2
  C2 --> S

locale_compile_locale (locale_support.c:4573) 가 Expat 을 LDML XML 위로 돌리며 start_element_ok / end_element_ok 콜백 (locale_support.c:907) 으로 세 개의 내부 구조를 빌드한다 — LOCALE_DATA (달력 이름, 형식, 통화, 알파벳, 정규화 파라미터); 각 <collation> 마다 파싱된 규칙을 담은 COLL_TAILORING 과 후속 최적화된 가중치를 위한 opt_coll 슬롯을 가진 LOCALE_COLLATION; 그리고 로케일 간에 공유되는 데이터 (Unicode mapping 표가 로케일별로 복사되지 않도록 dedup).

LDML 규칙 (<rules>) 은 struct tailor_rule (locale_support.h:246) 로 파싱된다 — T_LEVEL level, anchor_buf, 논리적 위치 (r_pos_typeRULE_POS_FIRST_VAR, RULE_POS_LAST_NON_IGN, …) 또는 anchor 문자가 들어간 reference 버퍼 (r_buf), direction (after/before), 그리고 tailored 버퍼 t_buf. & a < b 같은 규칙은 level=PRIMARY, anchor=a, direction=AFTER, t_buf=b 가 된다. 다중 문자 규칙 (& ch <<< Ch) 은 multiple_chars = true 로 표시되고, contraction 과 함께 tertiary case 오버레이를 표현한다.

CUBRID 은 절대 가중치 할당을 위한 vendor 확장 CUBRID_TAILOR_RULE (locale_support.h:279) 도 가지고 있다 — LDML 의 규칙 문법은 다른 문자에 대한 상대 anchor 만 지원하므로 U+0020 의 가중치를 [0.0.0.0] 로 set 이라고 말할 수 없다. <cubridrules> 요소가 그것을 풀어 준다.

<collation type="utf8_gen">
<settings id="32" strength="quaternary" caseLevel="on" caseFirst="upper" .../>
<cubridrules>
<set><scp>20</scp><w>[0.0.0.0]</w></set> <!-- SPACE is primary-ignorable -->
</cubridrules>
</collation>

파싱이 끝나면 genlocale 이 dedup 한다 — 두 콜레이션이 weights[] 에서 비트 단위로 동일하면 한 사본만 export 되고 다른 쪽의 coll_data_ref.coll_weights_ref 가 donor 심볼을 가리킨다 (locale_mark_duplicate_collations). locale_save_all_to_C_filestatic const 배열로 채워진 .c 파일과 export 심볼 coll_<id>_weights, coll_<id>_uca_w_l13, coll_<id>_contr_list 를 emit 한다. CMake 가 각각을 libcubrid_<locale>.so 로 컴파일한다.

런타임 로드 — lang_load_coll_from_lib

섹션 제목: “런타임 로드 — lang_load_coll_from_lib”

lang_initinit_user_localescubrid_locales.txt 를 walk 하며 lang_load_library (7272 줄 — dlopen 에 fallback 경로), lang_load_count_coll_from_lib, lang_load_get_coll_name_from_lib, 마지막으로 lang_load_coll_from_lib (7105 줄) 을 호출해 런타임 COLL_DATA 를 채운다.

함수는 SHLIB_GET_VAL / SHLIB_GET_ADDR 호출 시퀀스다 (dlsym 과 심볼 이름 stringification helper 로 확장됨) — coll_name, coll_id, coll_sett_strength, coll_w_count, coll_uca_exp_num, coll_count_contr. count_contr > 0 이면 coll_contr_list, coll_contr_min_size, coll_cp_first_contr_* 를 가져온다. uca_opt.sett_expansionscoll_uca_w_l13 (그리고 quaternary 면 coll_uca_w_l4) 를 가져온다. 그렇지 않으면 더 단순한 coll_weights 를 가져온다.

_W_REF 변종은 빌드 타임 머지 단계의 dedup 포워딩을 resolve 한다 — 이 콜레이션의 weights[] 가 다른 콜레이션과 머지되어 있으면 _ref 심볼이 donor 를 가리킨다.

비교기 바인딩이 그 뒤에 따라온다 — expansion 이 있는 UCA 콜레이션은 lang_strcmp_utf8_uca/lang_strmatch_utf8_uca 를 받고, contraction 만 있으면 _w_contr 변종을, 가중치 재배치만 있으면 lang_fastcmp_byte 또는 lang_strcmp_utf8 를 받는다.

콜레이션의 MD5 (COLL_DATA.checksum) 는 세션 핸드셰이크 시점에 lang_check_coll_compat (language_support.h:348) 로 검증된다 — 이것이 데이터가 흐르기 전에 client 와 server 가 다른 로케일 라이브러리에 링크된 상황을 잡는 방법이다.

B+Tree 는 가장 무거운 소비자다 — 모든 키 insert, 모든 range scan, 모든 페이지 split. btree_compare_individual_key_value (btree.c:19602) 가 NULL 정렬 (TP_DOMAIN.is_desc 가 제어) 을 처리한 다음, key_domain->type->cmpval (key1, key2, 2, 1, NULL, key_domain->collation_id) 로 dispatch 한다. 콜레이션은 인덱스의 TP_DOMAIN.collation_id 에서 시작해 type 의 cmpval (예 — pr_varchar_cmpval) 을 거쳐 QSTR_COMPARE 매크로에 흘러들어간다. QSTR_COMPARE(LANG_GET_COLLATION(id))->fastcmp(...) 로 확장된다. 릴리즈 빌드에서는 LANG_GET_COLLATION(id)lang_Collations[id] 다 — 인덱스된 로드 한 번과 간접 호출 한 번.

페이지 split 은 last_left <= K < first_right 를 만족하는 separator K 가 필요하다 — split_key 콜백. UTF-8 binary 의 경우 lang_split_key_utf8 가 두 쪽을 모두 walk 하다가 바이트 위치 i 에서 발산하면 str2 의 그 바이트까지의 prefix 를 돌려준다. UCA 콜레이션의 경우 두 바이트 시퀀스가 동등하게 비교될 수 있으므로 (NFC vs NFD) lang_split_key_w_exp 가 가중치 스트림을 대신 walk 한다.

next_coll_seq 는 range-scan 경계 증가를 위한 이보다 strict 하게 큰 가장 작은 키 를 만든다 — binary 면 마지막 바이트를 증가, UCA 면 codepoint 별로 next_cp[] 를 walk.

mht2str 는 hash join 과 OID 리스트 디렉토리를 먹인다. 콜레이션 동등성과 일치해야 한다 ('foo' 의 CI 해시는 'FOO' 의 해시와 같아야 한다). 그래서 raw 바이트가 아닌 가중치를 walk 한다. lang_mht2str_byte 는 모든 바이트를 mod 소수로 합한다. lang_mht2str_utf8_exp 는 UCA L1 가중치를 walk 한다.

flowchart TD
  Q[SQL: SELECT ... WHERE name = 'foo'] --> P[Parser: pt_check_collations]
  P --> X[XASL: collation_id propagated to expression]
  X --> E[Executor: pr_varchar_cmpval]
  E --> M[QSTR_COMPARE macro]
  M --> L[LANG_GET_COLLATION coll_id]
  L --> V[lang_Collations 256 vtable]
  V --> F[lang_collation->fastcmp pointer]
  F --> A[lang_fastcmp_byte / lang_strcmp_utf8 / lang_strcmp_utf8_uca]
  A --> R[int 비교 결과]

  B[B+Tree: btree_search] --> E
  S[Sort: ext_sort] --> E
  H[Hash join: heap_hash] --> V
  V -- mht2str --> H

LIKE, MATCH, REGEXP_LIKE 모두 strmatch 로 깔때기처럼 모인다. 패턴 해석은 db_string_like (string_opfunc.c) 에 있지만, 문자별 매치는 콜레이션의 strmatch 다. 와일드카드 의미.

  • _ 는 정확히 한 문자 (codepoint, 바이트가 아님) 를 매치.
  • % 는 0 개 이상의 문자를 매치.
  • escape 문자 (ESCAPE '\\' 절) 는 다음 문자를 literal 로 격하시킨다.

UTF-8 binary 의 경우 lang_strmatch_utf8 (2830 줄) 이 패턴과 source 양쪽에서 codepoint 를 walk 하면서, % 위에서는 source 를 재귀적으로 내려간다. escape 문자는 바이트 단위 비교 (memcmp (str2, escape, str2_next - str2) == 0) 다 — 멀티바이트가 될 수 있다.

COLL_OPT.allow_like_rewrite 플래그 (language_support.h:144) 는 옵티마이저가 col LIKE 'foo%' 를 인덱스 친화적인 pushdown 을 위해 col >= 'foo' AND col < 'fop' 로 재작성할 수 있는지를 제어한다. CS 콜레이션 (다음 codepoint 를 붙이는 것이 순서를 보존) 에서는 true, CI 콜레이션 ('FOO''foo''fop' 사이에 정렬될 수 있어 재작성을 깬다) 에서는 false 다.

CHAR 와 VARCHAR 의미는 SQL 표준의 PAD SPACE / NO PAD 구분을 따른다. CHAR(10) 가 'foo' 를 들고 있을 때, 저장 레이어가 10 바이트로 U+0020 (또는 로케일의 SPACE 문자) 으로 패딩한다. CHAR 와 VARCHAR 를 비교할 때는 trailing space 가 무시된다 — 'foo ' = 'foo' 가 참이다.

이는 모든 비교기에 ignore_trailing_space 를 흘려 보내며 구현된다. 가중치 표의 weights_tinext_cp_ti 변종은 SPACE 가 가중치 0 을 갖도록 미리 빌드되어 있어, 비교기는 hot loop 에서 special case 를 만들 필요가 없다. _ti 를 쓸 결정은 type-coercion 시점에 일어난다.

// QSTR_COMPARE call site, e.g. in db_string_compare
QSTR_COMPARE (coll_id, lhs_buf, lhs_size, rhs_buf, rhs_size,
/*ignore_trailing_space=*/ tp_is_padded_string(domain1, domain2));

binary (coll_Binary, ID 9) 에서는 답이 항상 아니오, 절대 무시 안 함 이다 — 바이너리 문자열은 바이트 단위로 정확하다.

casing 에는 네 개의 distinct 한 진입점이 있다. 미묘하게 다른 동작을 한다.

FunctionUsed forSpecial-cased Turkish?
intl_lower_string (intl_support.c:1684)LOWER(s), UPPER(s) SQL 함수yes (alphabet 에 lang_lower_i_TR)
intl_identifier_lower (intl_support.c:2960)식별자 이름 resolutionno (ident_alphabet 사용)
intl_identifier_casecmp (intl_support.c:2783)폴딩 없이 식별자 비교yes (per locale rules)
intl_identifier_casecmp_w_size명시적 사이즈로 식별자 비교yes

intl_lower_string 은 UTF-8 스트림을 walk 하면서 codepoint 를 가져와 alphabet->lower_cp[cp * lower_multiplier] 로 인덱스하고, 결과를 하나 또는 그 이상의 codepoint 로 다시 UTF-8 에 emit 한다. 사이즈 차이는 intl_lower_string_size 가 미리 계산해 caller 가 적절히 할당할 수 있게 한다.

lower_multiplier ≥ 2 케이스는 드물지만 실제로 존재한다 — 'İ' (U+0130) 은 Unicode 기본 casing 에 따라 'i' + U+0307 (combining dot above) 로 소문자화된다. Unicode 기본 casing 을 원하는 터키-인지 로케일은 multiplier 가 2 다.

UNICODE_NORMALIZATION (locale_support.h:492) 이 unicodedata.txt 로부터 빌드된 NFC 합성 표를 들고 있다. 런타임 API 는 unicode_support.h 에 있다.

bool unicode_string_need_compose (const char *str_in, int size_in,
int *size_out, const UNICODE_NORMALIZATION *norm);
void unicode_compose_string (const char *str_in, int size_in,
char *str_out, int *size_out,
bool *is_composed, const UNICODE_NORMALIZATION *norm);

CUBRID 은 insert 시점에 자동 정규화를 하지 않는다 — 애플리케이션이 NFC 를 보낸다고 가정한다. compose API 는 client-side 사용 (libcs 만, #if !defined (SERVER_MODE)) 과 식별자 이름 정규화를 위한 parser 를 위해 노출된다. 콜레이션의 경우 UCA 표가 NFC 위에서 빌드되어 있으므로, NFC 가 아닌 문자열을 NFC 문자열과 비교하면 mis-compare 할 수 있다. 이는 제약으로 문서화되어 있다.

런타임은 환경 변수와 cubrid_locales.txt 를 쓴다.

  • CUBRID_CHARSET / 시스템 로케일 → lang_charset(), 시스템 코드셋.
  • CUBRID_LANGcubrid.msg 의 메시지 언어.
  • cubrid_locales.txt ($CUBRID/conf/ 아래) → 로드할 LDML 로케일 목록. 각 항목이 LDML XML 과 사전 컴파일된 공유 라이브러리 경로를 명명한다.

시스템 콜레이션 LANG_SYS_COLLATIONlang_charset() 으로부터 도출된다.

// LANG_GET_BINARY_COLLATION — src/base/language_support.h:121
#define LANG_GET_BINARY_COLLATION(c) \
((c) == INTL_CODESET_UTF8 ? LANG_COLL_UTF8_BINARY : \
((c) == INTL_CODESET_KSC5601_EUC ? LANG_COLL_EUCKR_BINARY : \
((c) == INTL_CODESET_ISO88591 ? LANG_COLL_ISO_BINARY : \
LANG_COLL_BINARY)))

명시적인 COLLATE 절이 없는 컬럼은 이 콜레이션을 상속한다. 기본 DB charset 은 cubrid createdb 시점에 설정 가능하고 db_root 시스템 카탈로그 행에 영속된다.

binary 연산자 (=, <, LIKE) 에 서로 다른 콜레이션의 두 문자열이 주어졌을 때, 엔진은 한쪽을 고른다 — 단, 적어도 한쪽이 coercible 일 때만이다. 빌트인 binary 콜레이션은 coercible 이다 (LANG_IS_COERCIBLE_COLL 이 받아들임). 명시적으로 콜레이션이 지정된 값은 그렇지 않다. 매크로.

// LANG_RT_COMMON_COLL — src/base/language_support.h:65
#define LANG_RT_COMMON_COLL(c1, c2, coll) do { \
coll = -1; \
if ((c1) == (c2)) coll = (c1); /* trivial */ \
else if (LANG_IS_COERCIBLE_COLL(c1)) { \
if (!LANG_IS_COERCIBLE_COLL(c2)) coll = (c2); \
else if ((c2) == LANG_COLL_ISO_BINARY) coll = (c2); \
} else if (LANG_IS_COERCIBLE_COLL(c2)) coll = (c1); \
} while (0)

결과 coll == -1 은 공통 콜레이션 없음 을 뜻한다 — 엔진은 ER_QSTR_INCOMPATIBLE_COLLATIONS 를 발생시킨다. MySQL 의 “Illegal mix of collations 또는 Postgres 의 could not determine which collation to use” 의 CUBRID 대응물이다.

  • intl_convert_charset 는 stub 이다. 사용자 레벨 CONVERT(s USING charset) 은 지원되지 않는다 — parser/coercion 레이어의 pairwise 함수만 동작한다.
  • UTF-16 또는 UTF-32 가 없다 — wire 와 storage 는 UTF-8 전용이다.
  • 입력 시점의 자동 NFC 정규화가 없다. UCA 콜레이션 컬럼의 NFD 텍스트는 조용히 잘못 정렬된 인덱스 entry 를 만든다.
  • 콜레이션당 단일 MD5 체크섬만 있다. Postgres 의 pg_collation.collversion 인덱스 무효화 hook 같은 대응물은 없다.
  • 코드셋 집합이 4 개로 고정되어 있다. GBK 나 Shift-JIS 를 추가하려면 enum intl_codeset, 비교기 family, check-validity 함수, 변환 쌍을 모두 건드려야 한다.

레이어 케이크 순서 — 바이트 → codepoint → collation element → vtable dispatch → 소비자.

  1. intl_support.henum intl_codeset, INTL_NEXT_CHAR, INTL_CODESET_MULT.
  2. intl_support.cintl_utf8_to_cp (3754), intl_cp_to_utf8 (3661), intl_back_utf8_to_cp (3812), intl_check_utf8 (3950).
  3. language_support.h:159struct lang_collation (vtable).
  4. language_support.cbuilt_In_collations[] (830) → lang_init_builtin (850) → 개별 정적 선언 (696, 715, 736, 755 줄…).
  5. language_support.c:5453lang_fastcmp_byte (hot path).
  6. language_support.c:2830lang_strmatch_utf8 (codepoint walker).
  7. language_support.c:4368lang_strmatch_utf8_uca_w_coll_data (UCA driver) → lang_get_uca_w_l13 (3376).
  8. locale_support.hstruct coll_data (354), struct coll_tailoring (391); 공유 라이브러리 ABI 는 locale_lib_common.h.
  9. language_support.c:7105lang_load_coll_from_lib (런타임 바인딩).
  10. string_opfunc.h:59-74QSTR_COMPARE, QSTR_MATCH, QSTR_NEXT_ALPHA_CHAR, QSTR_SPLIT_KEY.
  11. btree.c:19602btree_compare_individual_key_value (소비자).
  • src/base/intl_support.h / intl_support.c — 코드셋 정의, UTF-8/EUC-KR/ISO 변환, 검증, 식별자 casing.
  • src/base/language_support.h / language_support.cLANG_COLLATION vtable, 빌트인 콜레이션, 로케일 로딩, 시스템 콜레이션/charset.
  • src/base/locale_support.h / locale_support.c — LDML parser, genlocale 빌드 타임 머시너리, COLL_DATACOLL_TAILORING.
  • src/base/locale_lib_common.h — 로케일 공유 라이브러리 ABI — COLL_CONTRACTION, UNICODE_MAPPING, CONV_CP_TO_BYTES.
  • src/base/unicode_support.h / unicode_support.c — NFC/NFD 합성과 분해.
  • src/base/charset_converters.h — libiconv 의 single/double-byte 변환표 형식 (Summary16, ksc5601.h, jisx0212.h).
  • src/query/string_opfunc.hQSTR_COMPARE, QSTR_MATCH, QSTR_NEXT_ALPHA_CHAR, QSTR_SPLIT_KEY 매크로.
  • src/storage/btree.cbtree_compare_individual_key_value, B+Tree 의 콜레이션 인지 키 비교기.
  • locales/data/ldml/cubrid_*.xml — LDML 로케일 정의. vendor 확장은 common_collations.xml.
  • locales/data/ducet.txt — Default Unicode Collation Element Table.
  • locales/data/unicodedata.txt — 알파벳과 정규화 생성을 위한 Unicode 문자 데이터.
  • locales/data/codepages/CP949.TXT, CP932.TXT, 등 — TEXT_CONVERSION 을 위한 비-Unicode 코드페이지 표.
  • locales/make_locale.sh — 로케일별로 genlocale 을 invoke 하는 빌드 진입점.