(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_codeset | src/base/intl_support.h | 175 |
INTL_CODESET_MULT | src/base/intl_support.h | 77 |
INTL_NEXT_CHAR | src/base/intl_support.h | 99 |
intl_Len_utf8_char (decl) | src/base/intl_support.h | 173 |
intl_utf8_to_cp | src/base/intl_support.c | 3754 |
intl_cp_to_utf8 | src/base/intl_support.c | 3661 |
intl_back_utf8_to_cp | src/base/intl_support.c | 3812 |
intl_check_utf8 | src/base/intl_support.c | 3950 |
intl_check_euckr | src/base/intl_support.c | 4239 |
intl_fast_iso88591_to_utf8 | src/base/intl_support.c | 4932 |
intl_euckr_to_utf8 | src/base/intl_support.c | 5101 |
intl_utf8_to_euckr | src/base/intl_support.c | 5256 |
intl_convert_charset | src/base/intl_support.c | 944 |
intl_char_count / intl_char_size | src/base/intl_support.c | 974 / 1021 |
intl_identifier_casecmp | src/base/intl_support.c | 2783 |
struct lang_collation | src/base/language_support.h | 159 |
struct lang_locale_data | src/base/language_support.h | 190 |
struct db_charset (lang_Db_charsets) | src/base/language_support.h:128 / .c:171 | |
enum LANG_COLL_* (built-in IDs) | src/base/language_support.h | 105 |
LANG_GET_BINARY_COLLATION | src/base/language_support.h | 121 |
LANG_RT_COMMON_COLL | src/base/language_support.h | 65 |
built_In_collations[] | src/base/language_support.c | 830 |
lang_init_builtin | src/base/language_support.c | 850 |
register_collation | src/base/language_support.c | 1568 |
lang_get_collation | src/base/language_support.c | 1648 |
lang_get_collation_by_name | src/base/language_support.c | 1678 |
lang_load_coll_from_lib | src/base/language_support.c | 7105 |
lang_load_library | src/base/language_support.c | 7272 |
lang_fastcmp_byte | src/base/language_support.c | 5453 |
lang_fastcmp_binary | src/base/language_support.c | 6415 |
lang_strcmp_utf8 | src/base/language_support.c | 2807 |
lang_strmatch_utf8 | src/base/language_support.c | 2830 |
lang_strcmp_utf8_uca | src/base/language_support.c | 4321 |
lang_strmatch_utf8_uca_w_coll_data | src/base/language_support.c | 4368 |
lang_strmatch_utf8_uca_w_level | src/base/language_support.c | 3583 |
lang_get_uca_w_l13 | src/base/language_support.c | 3376 |
lang_get_contr_for_string | src/base/language_support.c | 3296 |
lang_init_coll_en_cs / _en_ci | src/base/language_support.c | 5304 / 5346 |
struct coll_data | src/base/locale_support.h | 354 |
struct coll_tailoring | src/base/locale_support.h | 391 |
struct uca_options | src/base/locale_support.h | 315 |
struct alphabet_data | src/base/locale_support.h | 437 |
struct text_conversion | src/base/locale_support.h | 464 |
struct unicode_normalization | src/base/locale_support.h | 492 |
locale_compile_locale | src/base/locale_support.c | 4573 |
COLL_CONTRACTION | src/base/locale_lib_common.h | 44 |
QSTR_COMPARE macro | src/query/string_opfunc.h | 59 |
QSTR_MATCH macro | src/query/string_opfunc.h | 62 |
QSTR_NEXT_ALPHA_CHAR macro | src/query/string_opfunc.h | 68 |
QSTR_SPLIT_KEY macro | src/query/string_opfunc.h | 71 |
btree_compare_individual_key_value | src/storage/btree.c | 19602 |
학술적 배경
섹션 제목: “학술적 배경”텍스트를 다루는 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 는 의존성이 아니다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”- 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 명칭 매핑
섹션 제목: “이론 ↔ CUBRID 명칭 매핑”| 이론적 개념 | CUBRID 명칭 |
|---|---|
| 인코딩 enum | enum 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) |
| 콜레이션 vtable | struct lang_collation (language_support.h:159) |
| UCA 가중치 표 | struct coll_data::uca_w_l13 (locale_support.h:354) |
| LDML tailoring | struct 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) |
CUBRID의 구현
섹션 제목: “CUBRID의 구현”모델은 여섯 개의 구조체로 정리된다. 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_server와cubridsa는 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
섹션 제목: “코드셋 — enum intl_codeset”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) 의 여섯 행이다.
| name | introducer | space (pad) | id | space_size |
|---|---|---|---|---|
ascii | — | | ASCII | 1 |
raw-bits | — | "" | RAW_BITS | 1 |
raw-bytes | _binary | "" | BINARY | 1 |
iso8859-1 | _iso88591 | | ISO88591 | 1 |
ksc-euc | _euckr | "\241\241" (U+3000 ideographic) | KSC5601_EUC | 2 |
utf-8 | _utf8 | | UTF8 | 1 |
space_char 가 CHAR 의 고정 길이 패딩이다. EUC-KR 은 U+3000 (ideographic space, 두 바이트) 을 쓰고, 나머지는 ASCII SPACE 를 쓴다. introducer 는 문법이 _utf8'literal' 형태로 받아들이는 표식이다.
UTF-8 코드포인트 변환
섹션 제목: “UTF-8 코드포인트 변환”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 규칙) 과LIKEescape lookback 에서 사용.
이미 검증된 스트림에서는 표 기반 INTL_NEXTCHAR_UTF8 매크로 (intl_support.h:67) 가 intl_utf8_to_cp 보다 빠르다 — intl_Len_utf8_char[lead_byte] 가 바이트 길이를 돌려준다. UCA inner loop 안에서 사용된다 (입력 검증은 이미 끝났기 때문이다).
UTF-8 검증
섹션 제목: “UTF-8 검증”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 인 것이다.
Charset 변환 파이프라인
섹션 제목: “Charset 변환 파이프라인”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) 함수의 삼각형을 제공한다.
| Source | Target | Function | File:Line |
|---|---|---|---|
| ISO-8859-1 | UTF-8 | intl_fast_iso88591_to_utf8 | intl_support.c:4933 |
| ISO-8859-1 | EUC-KR | intl_iso88591_to_euckr | (similar) |
| EUC-KR | ISO-8859-1 | intl_euckr_to_iso88591 | intl_support.c:4982 |
| EUC-KR | UTF-8 | intl_euckr_to_utf8 | intl_support.c:5102 |
| UTF-8 | ISO-8859-1 | intl_utf8_to_iso88591 | intl_support.c |
| UTF-8 | EUC-KR | intl_utf8_to_euckr | intl_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 을 강제할 때만 호출된다.
LANG_COLLATION — 비교기 vtable
섹션 제목: “LANG_COLLATION — 비교기 vtable”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 를 쌍별로 한 개의 구체적 함수 포인터로 해소하는 자리다. 빌트인 콜레이션 열 개의 와이어링은 다음과 같다.
| ID | Name | Codeset | fastcmp | strmatch | split_key | mht2str |
|---|---|---|---|---|---|---|
| 0 | iso88591_bin | ISO-8859-1 | lang_fastcmp_byte | lang_strmatch_byte | lang_split_key_iso | lang_mht2str_default |
| 1 | utf8_bin | UTF-8 | lang_fastcmp_byte | lang_strmatch_utf8 | lang_split_key_utf8 | lang_mht2str_byte |
| 2 | iso88591_en_cs | ISO-8859-1 | lang_fastcmp_byte | lang_strmatch_byte | lang_split_key_byte | lang_mht2str_byte |
| 3 | iso88591_en_ci | ISO-8859-1 | lang_fastcmp_byte | lang_strmatch_byte | lang_split_key_byte | lang_mht2str_byte |
| 4 | utf8_en_cs | UTF-8 | lang_fastcmp_byte | lang_strmatch_utf8 | lang_split_key_utf8 | lang_mht2str_byte |
| 5 | utf8_en_ci | UTF-8 | lang_fastcmp_byte | lang_strmatch_utf8 | lang_split_key_utf8 | lang_mht2str_byte |
| 6 | utf8_tr_cs | UTF-8 | lang_strcmp_utf8 | lang_strmatch_utf8 | lang_split_key_utf8 | lang_mht2str_utf8 |
| 7 | utf8_ko_cs | UTF-8 | lang_strcmp_utf8 | lang_strmatch_utf8 | lang_split_key_utf8 | lang_mht2str_utf8 |
| 8 | euckr_bin | EUC-KR | lang_fastcmp_byte | lang_strmatch_byte | lang_split_key_euckr | lang_mht2str_ko |
| 9 | binary | RAW_BYTES | lang_fastcmp_binary | lang_strmatch_binary | lang_split_key_binary | (default) |
표에서 세 가지를 짚어 둘 만하다.
utf8_bin이 UTF-8 인지 비교가 아니라lang_fastcmp_byte를 재사용한다. 이는 옳다 — UTF-8 의 설계가 lexicographic 바이트 비교가 codepoint 비교와 일치한다는 것을 보장한다. 바이트 비교기는 빡빡한 inner loop 라서 어떤 UTF-8 walker 보다도 빠르다.utf8_bin의strmatch는lang_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_collation 은 coll->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 안에 분기는 없다.
lang_fastcmp_byte — hot path
섹션 제목: “lang_fastcmp_byte — hot path”language_support.c:5453. binary, ISO-8859-1, EUC-KR-binary, UTF-8-binary 키 위의 모든 바이트 레벨 B+Tree 비교와 모든 문자열 연산자가 소비하는 함수다. 세 단계로 동작한다.
- 공통 prefix 루프 —
min(size1, size2)바이트. 각 바이트 —SPACE → ZERO의 special case, 그렇지 않으면weight[byte].c1 - c2비교. 0 이 아니면 반환. - trailing-sensitive tail (
!ignore_trailing_space) — 사이즈가 다르면size1 - size2반환. - trailing-insensitive tail — 더 긴 쪽을 walk. 각 가중치를
ZERO와 비교. 0 이 아닌 첫 차이가 승자.
미묘한 점들이 있다. SPACE 검사는 어느 가중치 표 변종이 로드되었든 동작하도록 루프 안에 있다. weight[*s1] 은 256/352-entry 표에 대한 L1 캐시 lookup 한 번이다. trailing-space tail 은 두 개의 specialized loop 중 하나를 side 별로 고른다 — 분기 예측기가 안정된 target 을 본다.
lang_strcmp_utf8 — codepoint walker
섹션 제목: “lang_strcmp_utf8 — codepoint walker”language_support.c:2830 (2807 의 lang_strcmp_utf8 가 is_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 (primary) 비교를
lang_strmatch_utf8_uca_w_level (cd, 0, ...)로. 0 이 아니면 반환. - strength 가
TAILOR_PRIMARY뿐이면 —sett_caseLevel일 때 선택적으로 레벨 3 (case) 수행.sett_caseFirst == 1(upper-first) 이면 부호를 뒤집는다. - 레벨 2 (accent).
sett_backwards(프랑스어 L2 reverse) 면lang_back_strmatch_utf8_uca_w_level호출, 아니면 forward. - 레벨 3 (case).
- 레벨 4 (variable/quaternary,
UCA_L4_W16 비트 가중치) —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 bitINTL_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_iso88591 → lc_Korean_utf8 → lc_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 를 요구한다 (그래서 SELECT 와 select 는 같은 키워드). 그러나 터키 로케일의 풀 알파벳은 'I' 를 'ı' 로 폴딩한다 — SQL 키워드를 깨뜨린다. intl_identifier_casecmp (2783 줄) 은 ident_alphabet 을 쓰고, db_string_lower 는 alphabet 을 쓴다.
LDML 빌드 파이프라인 — genlocale
섹션 제목: “LDML 빌드 파이프라인 — genlocale”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_type — RULE_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_file 은 static 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_init → init_user_locales 가 cubrid_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_expansions 면 coll_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 통합
섹션 제목: “B+Tree 통합”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 와 패턴 매칭
섹션 제목: “LIKE 와 패턴 매칭”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 다.
Trailing-space 처리
섹션 제목: “Trailing-space 처리”CHAR 와 VARCHAR 의미는 SQL 표준의 PAD SPACE / NO PAD 구분을 따른다. CHAR(10) 가 'foo' 를 들고 있을 때, 저장 레이어가 10 바이트로 U+0020 (또는 로케일의 SPACE 문자) 으로 패딩한다. CHAR 와 VARCHAR 를 비교할 때는 trailing space 가 무시된다 — 'foo ' = 'foo' 가 참이다.
이는 모든 비교기에 ignore_trailing_space 를 흘려 보내며 구현된다. 가중치 표의 weights_ti 와 next_cp_ti 변종은 SPACE 가 가중치 0 을 갖도록 미리 빌드되어 있어, 비교기는 hot loop 에서 special case 를 만들 필요가 없다. _ti 를 쓸 결정은 type-coercion 시점에 일어난다.
// QSTR_COMPARE call site, e.g. in db_string_compareQSTR_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 의 다섯 codepoint 춤사위
섹션 제목: “casing 의 다섯 codepoint 춤사위”casing 에는 네 개의 distinct 한 진입점이 있다. 미묘하게 다른 동작을 한다.
| Function | Used for | Special-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) | 식별자 이름 resolution | no (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_LANG→cubrid.msg의 메시지 언어.cubrid_locales.txt($CUBRID/conf/아래) → 로드할 LDML 로케일 목록. 각 항목이 LDML XML 과 사전 컴파일된 공유 라이브러리 경로를 명명한다.
시스템 콜레이션 LANG_SYS_COLLATION 은 lang_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 시스템 카탈로그 행에 영속된다.
Coercion — LANG_RT_COMMON_COLL
섹션 제목: “Coercion — LANG_RT_COMMON_COLL”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 → 소비자.
intl_support.h—enum intl_codeset,INTL_NEXT_CHAR,INTL_CODESET_MULT.intl_support.c—intl_utf8_to_cp(3754),intl_cp_to_utf8(3661),intl_back_utf8_to_cp(3812),intl_check_utf8(3950).language_support.h:159—struct lang_collation(vtable).language_support.c—built_In_collations[](830) →lang_init_builtin(850) → 개별 정적 선언 (696, 715, 736, 755 줄…).language_support.c:5453—lang_fastcmp_byte(hot path).language_support.c:2830—lang_strmatch_utf8(codepoint walker).language_support.c:4368—lang_strmatch_utf8_uca_w_coll_data(UCA driver) →lang_get_uca_w_l13(3376).locale_support.h—struct coll_data(354),struct coll_tailoring(391); 공유 라이브러리 ABI 는locale_lib_common.h.language_support.c:7105—lang_load_coll_from_lib(런타임 바인딩).string_opfunc.h:59-74—QSTR_COMPARE,QSTR_MATCH,QSTR_NEXT_ALPHA_CHAR,QSTR_SPLIT_KEY.btree.c:19602—btree_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.c—LANG_COLLATIONvtable, 빌트인 콜레이션, 로케일 로딩, 시스템 콜레이션/charset.src/base/locale_support.h/locale_support.c— LDML parser,genlocale빌드 타임 머시너리,COLL_DATA와COLL_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.h—QSTR_COMPARE,QSTR_MATCH,QSTR_NEXT_ALPHA_CHAR,QSTR_SPLIT_KEY매크로.src/storage/btree.c—btree_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 하는 빌드 진입점.