(KO) CUBRID 시스템 카탈로그 클래스 — 데이터 주도 정의, 부트스트랩 설치, 시스템 뷰 query spec
목차
학술적 배경
섹션 제목: “학술적 배경”관계형 엔진은 자기 자신의 스키마를 SQL로 노출해야 한다. 어떤 클래스가 있고, 어떤 컬럼을 가지며, 어떤 인덱스가 걸려 있고, 누가 소유주이며, 어떤 사용자가 어떤 권한을 갖는가? 이 답은 평범한 SELECT로 질의 가능해야 한다 — 그렇지 않으면 \d(psql), DESCRIBE(MySQL), GUI 클라이언트 같은 도구가 동작하지 않는다. 이것이 시스템 카탈로그를 관계로 다룬다는 원칙으로, System R 1979(Astrahan et al., System R: Relational Approach to Database Management, ACM TODS)까지 거슬러 올라가고 Hellerstein과 Stonebraker가 Anatomy of a Database System 4장에서 명료화한다 — 카탈로그는 사용자 테이블과 동일한 실행 스택으로 접근되는 또 하나의 테이블 모음일 뿐이다.
엔진이 그 표면을 실제로 어떻게 만들어 내는지를 결정하는 세 가지 텍스트북 아이디어가 있다.
스키마는 곧 데이터. 모든 시스템 카탈로그 테이블은 고정된 모양을 갖는다. _db_class는 class_name, owner, class_type, inst_attr_count 등 정해진 컬럼을 가진다. 이 모양을 표현하는 두 가지 방법이 있다. 손으로 코딩하는 방식은 명령형 C에 db_add_attribute (db_class, class_name, "varchar(255), NULL) 호출을 줄줄이 늘어놓는다 — 테이블당, 컬럼당 한 줄씩. 결과 코드는 정확하지만 읽기 힘들고, 진화시키기 어렵고, 전체로서 검증하기 불가능하다. 데이터 주도 방식은 각 클래스를 값으로 표현한다 — name, attributes 벡터, constraints 벡터, authorization 블록을 가진 구조체로. 그리고 단일 generic builder가 이 벡터를 소비해 클래스를 만든다. Codd의 관계 모델은 이미 스키마는 데이터다”라고 말했고, 이것은 그 아이디어를 엔진 내부에서 구현으로 표현한 것이다.
부트스트랩 닭과 달걀 문제. 카탈로그는 카탈로그가 읽히기 전에 이미 존재해야 한다. _db_class는 _db_class라는 클래스를 기술한다 — 그 정의를 읽으려면 이미 그것을 읽을 수 있어야 한다. 모든 엔진이 이 순환을 같은 방식으로 끊는다. createdb 시점에 엔진이 루트 앵커에 하드코딩된 지식을 심는다(System R의 SYSTABLES, Oracle의 부트스트랩 segment, PostgreSQL의 pg_class 초기 heap, CUBRID의 root class). 그 앵커를 발판으로 그 이후 모든 시스템 클래스를 사용자가 발급할 수 있는 똑같은 DDL 연산으로 설치해 나간다. 그래서 설치 경로는 사용자 CREATE TABLE이 타는 것과 같은 db_create_class / smt_add_attribute / sm_update_class다 — 호출을 구동하는 데이터만 특권적이다.
디스크 카탈로그와 SQL 카탈로그의 분리. Petrov의 Database Internals §1은 엔진 내부 메타데이터(옵티마이저와 실행기가 직접 소비하는 압축 디스크 레코드 — OR_CLASSREP, CLS_INFO, BTREE_STATS)와 SQL에서 보이는 카탈로그(같은 정보를 SQL 표면으로 노출하는 _db_class, _db_attribute, _db_index)를 구분한다. 둘은 중복이 아니다. 디스크 카탈로그는 모든 plan compile에서 읽히므로 deserialise가 싸야 한다. SQL 카탈로그는 평범한 스캔과 조인으로 질의되므로 다른 어떤 테이블과도 같은 모양이어야 한다. CUBRID는 이 분리를 명시적으로 표현해서 cubrid-catalog-manager(디스크 측)는 DDL 인프라로 두고, 이 모듈(SQL에서 보이는 시스템 클래스)은 카탈로그 façade로 둔다 — 같은 메타데이터의 두 레이어가 DDL 파이프라인에 의해 동기화된다.
이 셋이 결합해 하나의 설계가 된다. 각 시스템 클래스를 system_catalog_definition 값으로 인코딩하고, 평범한 DDL을 발급하는 generic builder로 설치하고, createdb 시점에 root-class 앵커를 한 번 install을 돌린다. schema_system_catalog_install.cpp가 데이터고, schema_system_catalog_builder.cpp가 엔진이고, catcls_install이 오케스트레이션이다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”네 가지 메이저 엔진은 같은 SQL 표면 카탈로그를 서로 다른 부트스트랩 메커니즘으로 만들어 낸다.
PostgreSQL은 pg_catalog 스키마 아래 pg_class, pg_attribute, pg_index, pg_constraint를 평범한 heap 테이블로 둔다. 부트스트랩은 genbki.pl이 담당한다. 이 Perl 스크립트가 src/include/catalog/pg_*.h의 BKI_* 매크로 주석이 달린 C 구조체를 읽어 postgres.bki 스크립트(create/insert 지시문)를 생성하고, postgres 백엔드의 standalone --bootstrap 모드가 빈 클러스터를 그 스크립트를 실행해서 모든 시스템 테이블을 심는다. 부트스트랩 후 카탈로그는 사용자 테이블과 동일한 heap-and-btree 접근 메서드 위의 표준 RelationCacheLookup으로 읽힌다. 핵심: 스키마-는-데이터 문제를 빌드 타임 코드 생성기로 풀고, 런타임 프레임워크는 두지 않는다.
MySQL은 두 개의 병렬 표면을 가진다. INFORMATION_SCHEMA.*는 SQL 표준의 자기-기술이다. 각 INFORMATION_SCHEMA 테이블은 커스텀 storage engine(ha_information_schema)으로 구현되어 데이터 사전에서 즉석으로 행을 합성한다. 실제 디스크 측 카탈로그(8.0 이후)는 mysql 스키마 아래의 InnoDB 테이블(mysql.tables, mysql.columns, …)에 살고, mysqld --initialize가 SQL DDL 스크립트로 부트스트랩한다. performance_schema는 ring-buffer storage engine으로 구현된 별도의 런타임-상태 표면이다. 같은 개념적 카탈로그를 위한 세 레이어 — 부트스트랩 DDL, InnoDB-기반 데이터 사전, 가상 INFORMATION_SCHEMA.
Oracle은 데이터 사전을 SYS 소유의 고정된 테이블 집합(OBJ$, COL$, IND$, USER$)에 두고, 그 위에 C 배열을 관계로 투영하는 fixed table 레이어(X$KQFTA, X$KQFCO)를 얹는다. 사용자 표면은 DBA_*/ALL_*/USER_* 뷰 패밀리로, 모두 $ORACLE_HOME/rdbms/admin/catalog.sql에 정의되어 있다 — 100K줄짜리 SQL 스크립트가 CREATE DATABASE 시점에 실행되어 SYS.* 테이블을 만들고, seed 행을 채우고, 사전 뷰들을 그 위에 SQL CREATE VIEW로 정의한다.
SQL Server도 같은 모양이다. 고정된 base 카탈로그 테이블 집합(대부분 접근 불가 — sys.sysschobjs)에 더해, 서버 설치 시 함께 출하되는 read-only resource database mssqlsystemresource.mdf에 정의된 sys.* 카탈로그 뷰와 INFORMATION_SCHEMA.* 표면이 공개된다.
엔진들은 스키마-는-데이터가 어디에 사는지가 다르다 — Postgres의 BKI 파일(빌드 타임), MySQL의 mysql/InnoDB 테이블(initialize 타임), Oracle의 catalog.sql(createdb 타임), SQL Server의 resource db(설치 타임) — 그러나 동작은 같다. 메타-스키마를 데이터로 인코딩하고, 평범한 DDL 프리미티브로 한 번 install을 돌리고, 그 이후로는 카탈로그를 다른 어떤 테이블과도 똑같이 다룬다.
CUBRID이 자리 잡는 곳. CUBRID는 메타-스키마를 런타임에 C++ 코드로 인코딩한다. 클래스마다 cubschema::system_catalog_definition 값 하나, catcls_init에서 std::vector에 등록되고, catcls_install에서 단일 system_catalog_builder에 의해 소비된다. 데이터가 엔진 옆에 살므로(외부 스크립트를 함께 출하하고 버전을 맞출 필요가 없다) 사용자 CREATE TABLE이 타는 것과 같은 db_create_class / smt_add_attribute / sm_update_class 호출 경로를 통과한다. 시스템 뷰(db_class, db_attribute, …)는 query spec을 SQL 문자열 리터럴로 들고 있고, 바이너리에 컴파일되어, 기반 클래스가 빌드된 뒤 db_add_query_spec으로 설치된다. 결과는 엔진의 일부인 카탈로그 façade가 된다. 한 PR로 C++ 트리의 나머지와 함께 진화하고, 다른 어떤 테이블처럼 SELECT * FROM _db_class로 들여다 볼 수 있다.
CUBRID의 구현
섹션 제목: “CUBRID의 구현”모듈은 데이터 주도 프레임워크를 함께 구현하는 네 조각으로 갈라진다.
flowchart TB
subgraph constants["이름 — schema_system_catalog_constants.h"]
CN["CT_CLASS_NAME = '_db_class'<br/>CT_ATTRIBUTE_NAME = '_db_attribute'<br/>CT_INDEX_NAME = '_db_index'<br/>... 24개 클래스 + 22개 뷰 이름"]
end
subgraph defs["메타-스키마 — schema_system_catalog_definition.hpp"]
A["struct attribute<br/>(COLUMN | CLASS_METHOD | QUERY_SPEC)"]
C["struct constraint<br/>(PK | UNIQUE | INDEX | NOT_NULL)"]
G["struct grant<br/>+ struct authorization"]
SD["struct system_catalog_definition<br/>{ name, attributes[], constraints[],<br/>auth, row_initializer }"]
A --> SD
C --> SD
G --> SD
end
subgraph install["데이터 — schema_system_catalog_install.cpp + _query_spec.cpp"]
INIT["system_catalog_initializer::<br/>get_class() / get_attribute() / get_index() / ..."]
CLIST["std::vector clist (24개 클래스)"]
VCLIST["std::vector vclist (22개 뷰)"]
INIT --> CLIST
INIT --> VCLIST
end
subgraph builder["엔진 — schema_system_catalog_builder.cpp"]
B["system_catalog_builder::<br/>create_and_mark_system_class<br/>build_class<br/>build_vclass"]
end
subgraph entry["진입점 — schema_system_catalog.cpp + install.cpp"]
INITFN["catcls_init()"]
INSTALLFN["catcls_install()"]
INITFN --> CLIST
INITFN --> VCLIST
INSTALLFN --> B
end
SD --> INIT
CN --> INIT
CLIST --> INSTALLFN
VCLIST --> INSTALLFN
세 가지 성질이 이 설계를 읽히게 만든다.
하나의 표현, 두 명의 소비자. 동일한 system_catalog_definition이 클래스를 기술하기도 하고(build_class가 소비) 뷰를 기술하기도 한다(build_vclass가 소비). attribute_kind 디스크리미네이터가 경로를 가른다. COLUMN과 CLASS_METHOD는 smt_add_attribute / smt_add_class_method로 가고, QUERY_SPEC은 db_add_query_spec으로 간다. 뷰의 query spec은 그저 또 다른 attribute 종류일 뿐이라서, 뷰와 테이블이 정의 모양을 공유한다 — 조용하지만 표현력 있는 선택이다.
정의는 함수가 아니라 값이다. 옛 CUBRID 코드는 클래스마다 함수를 가졌다 — boot_define_class, boot_define_attribute, … 가 명령형으로 db_add_* 호출을 길게 늘어놓는 구조였다. 현재 모듈은 그 자리에 system_catalog_initializer::get_class()가 system_catalog_definition 값을 반환하도록 두었다. builder가 그 값으로부터 같은 명령형 시퀀스를 다시 끌어낸다. 이 중복은 의도적이다 — 메타-스키마에 새 기능이 필요할 때(예: dvalue_func 람다로 이미 표현된 default 값), 정의 구조체에 한 번, builder에 한 번만 넣으면 된다. 46군데에 흩어 넣을 일이 없다.
install은 안정된 파이프라인이고, 데이터가 변수다. catcls_install은 매번 똑같은 세 가지를 한다 — 모든 클래스 정의를 등록, 모든 클래스를 빌드, 모든 뷰를 빌드. 순서는 중요하다. 클래스가 먼저 존재해야 뷰가 query spec에서 그것을 참조할 수 있고, 첫 번째 패스에서 빈 MOP를 만들어 두면 두 번째 패스에서 다른 시스템 클래스를 참조하는 attribute를 안전하게 설치할 수 있다(예: _db_class.owner는 db_user 타입이라 db_user MOP가 먼저 알려져 있어야 한다). 이 두 패스 패턴은 catalog-manager가 디스크 카탈로그에서 쓰는 것과 같은 상호 재귀 fix다(카탈로그 파일을 먼저 할당한 뒤에 그것을 참조하는 행을 쓴다).
// catcls_install — schema_system_catalog_install.cpp (condensed)int catcls_install (void){ AU_DISABLE (save); au_set_user (Au_dba_user);
// Pass 1: 모든 시스템 클래스에 대해 빈 MOP 생성 for (i = 0; i < clist.size (); i++) class_mop[i] = catalog_builder::create_and_mark_system_class (clist[i].name);
// Pass 2: attribute, constraint, grant, row initializer 설치 for (i = 0; i < clist.size (); i++) catalog_builder::build_class (class_mop[i], clist[i].definition);
// Pass 3: 시스템 뷰 설치 (query spec이 시스템 클래스를 참조할 수 있다) for (i = 0; i < vclist.size (); i++) { MOP class_mop = catalog_builder::create_and_mark_system_class (vclist[i].name); catalog_builder::build_vclass (class_mop, vclist[i].definition); }
AU_ENABLE (save); clist.clear (); vclist.clear (); return error_code;}AU_DISABLE / AU_ENABLE 괄호와 au_set_user (Au_dba_user)는 부속물이 아니다. install은 아직 권한 행을 갖지 않은 테이블을 DDL을 돌리고, 이 시점의 createdb에는 DBA principal만 존재한다. install 동안 권한 검사를 끄는 것이 재귀를 종료시키는 유일한 길이다.
24개 시스템 클래스와 22개 시스템 뷰
섹션 제목: “24개 시스템 클래스와 22개 시스템 뷰”CUBRID이 노출하는 카탈로그 표면은 두 축으로 갈라진다 — 종류(heap 위의 진짜 클래스 vs. 그 위에 얹힌 SQL 뷰)와 관심사(스키마, 권한, 통계, 파티셔닝, PL, 복제, i18n, 서버 federation).
| 클래스 (24) | 관심사 | 비고 |
|---|---|---|
_db_class, _db_attribute, _db_domain, _db_method, _db_meth_sig, _db_meth_arg, _db_meth_file, _db_query_spec, _db_index, _db_index_key, _db_data_type | 핵심 스키마 | 모든 사용자 클래스를 자기-기술. _db_class는 그 자체로 24개 중 하나. |
db_root, db_user, db_password, db_authorization | 권한 | 선행 underscore 없는 유일한 그룹 — CREATE USER 같은 직접 DDL을 위해 사용자 측에서 보인다. |
_db_auth, _db_partition, _db_trigger, _db_serial | 기능 표면 | 객체별 권한, 파티션 맵, 트리거 레지스트리, 시퀀스 상태. |
_db_stored_procedure, _db_stored_procedure_args, _db_stored_procedure_code | PL 패밀리 | 저장 프로시저 레지스트리(소비처는 cubrid-pl-javasp.md, cubrid-pl-plcsql.md). |
_db_ha_apply_info | 복제 | 로그-applier 체크포인트 상태. |
_db_collation, _db_charset | i18n | 플랫폼별 libcubrid_collations.so에서 로딩됨(cubrid-charset-collation.md). |
_db_server | federation | DBLink 서버 카탈로그. |
_db_synonym | 이름 | 객체 별칭. |
dual | 유틸리티 | SELECT 1 FROM dual을 위한 한 행 테이블 — Oracle 호환. |
| 뷰 (22) | 기반 클래스 | 비고 |
|---|---|---|
db_class, db_direct_super_class, db_vclass | _db_class | 클래스 종류와 소유권으로 필터. |
db_attribute, db_attr_setdomain_elm | _db_attribute + _db_domain | set-domain 원소를 _db_domain으로 join. |
db_method, db_meth_arg, db_meth_arg_setdomain_elm, db_meth_file | 메서드 가족 | 현대 사용에서는 거의 비활성이지만 호환성을 위해 보존. |
db_index, db_index_key | 인덱스 가족 | B+Tree 메타데이터를 정렬된 행으로 추출. |
db_auth | _db_auth | class_of/grantor/grantee MOP를 읽기 좋은 이름으로 풀어냄. |
db_trigger, db_partition | trigger / partition | 카탈로그 플래그를 사람이 읽을 수 있는 enum으로 디코딩. |
db_stored_procedure, db_stored_procedure_args | SP 가족 | 사용자가 \sp로 보는 시그니처. |
db_serial, db_ha_apply_info | 기능 뷰 | 캐시된 상태. |
db_collation, db_charset | i18n 뷰 | UTF-8/ICU 디코딩됨. |
db_server, db_synonym | federation 뷰 | DBLink 서버 맵. |
24/22 분리는 또한 CUBRID이 Oracle 대 ANSI/ISO 관습으로부터 물려받은 이중 표면이다 — 선행 underscore 테이블은 엔진 사적 저장소, underscore 없는 뷰는 사용자가 읽는 표면이다.
인접 모듈과의 자리
섹션 제목: “인접 모듈과의 자리”flowchart LR DDL["DDL 파이프라인<br/>(cubrid-ddl-execution)"] CM["카탈로그 매니저<br/>(cubrid-catalog-manager)<br/>디스크: OR_CLASSREP, CTID"] SC["이 모듈<br/>(system-catalog-classes)<br/>SQL 표면: _db_class 외"] SHOW["SHOW 명령<br/>(cubrid-show-commands)<br/>런타임: 가상 스캔"] SM["스키마 매니저<br/>(cubrid-class-object)<br/>인메모리: SM_CLASS"] BOOT["boot_sr / createdb<br/>(cubrid-boot)"] BOOT -->|catcls_init + catcls_install| SC SC -->|행을 쓰는 곳| CM SC -.표면화 경로.-> SHOW DDL -->|컴파일을 위해 읽음| CM DDL -->|갱신을 위해 읽음| SC SM -->|_db_class 행을 소비| SC
- 이 모듈의 상류:
cubrid-boot이createdb동안catcls_init을 한 번 호출해 메타-스키마를 등록하고, 이어서catcls_install로 실제 DDL을 돌린다. - 빌드되어 들어가는 곳:
cubrid-catalog-manager. 모든db_create_class는 결국OR_CLASSREP을 디스크 카탈로그에 쓴다 — 행이 물리적으로 사는 곳은 그 자리다. 이 모듈은 façade이고, catalog-manager는 저장소다. - DDL & 스키마 영역의 인접 모듈:
cubrid-class-object(스키마 그래프,SM_CLASS)는 plan compile마다 같은 행을 읽어 인메모리 표현을 구성한다.cubrid-ddl-execution은 사용자가CREATE TABLE을 발급할 때 새 행을 쓴다. - 위로 표면화되는 곳:
cubrid-show-commands는 런타임 상태를 가상 스캔으로 노출하고, 이 모듈은 정적 스키마를_db_class등에 대한 진짜 heap 스캔으로 노출한다. 둘이 함께 system-catalog 섹션의 SQL을 통한 자기-기술 표면을 완성한다.
소스 코드 가이드
섹션 제목: “소스 코드 가이드”모듈 심볼은 파일 역할로 깔끔히 묶인다.
schema_system_catalog_constants.h — 모든 시스템 클래스/뷰 이름의 문자열 상수. raw 클래스 이름(CT_CLASS_NAME = "_db_class)과 뷰 이름(CTV_CLASS_NAME = db_class") 모두 여기 산다. 카탈로그를 이름으로 참조하는 곳마다 쓰인다(DDL 파싱, SHOW, info schema, 이 모듈).
schema_system_catalog.hpp/cpp — 최상위 API. catcls_init(정의 등록), catcls_install(install 실행), sm_is_system_class / sm_is_system_vclass(sm_system_class_names / sm_system_vclass_names 벡터에 대한 이름-소속 검사). catcls_add_data_type, catcls_add_charsets, catcls_add_collations row-initializer 진입점도 여기 선언되어 있고, _db_data_type, _db_charset, _db_collation 정의의 system_catalog_definition::row_initializer로 호출된다.
schema_system_catalog_definition.hpp/cpp — 메타-스키마 구조체.
// 메타-스키마 — schema_system_catalog_definition.hpp (condensed)enum class attribute_kind { COLUMN, CLASS_METHOD, QUERY_SPEC };
struct attribute { attribute_kind kind; std::string name; std::string type; // SQL 타입 문자열: "varchar(255)", "integer", ... std::function<int (DB_VALUE *)> dvalue_func; // 선택적 default};
struct constraint { DB_CONSTRAINT_TYPE type; // PK | UNIQUE | INDEX | NOT_NULL std::string name; std::vector<const char *> attribute_names; bool is_class_attributes;};
struct authorization { struct db_object *owner; // 보통 Au_dba_user std::vector<grant> grants; // (target_user, auth, with_grant_option)};
struct system_catalog_definition { std::string name; std::vector<attribute> attributes; std::vector<constraint> constraints; authorization auth; std::function<int (db_object *)> row_initializer; // 선택적 seed 데이터};attribute::type 필드는 TP_DOMAIN이 아니라 SQL 타입 문자열이다. 이 문자열은 사용자가 CREATE TABLE에 쓴 것과 똑같이 smt_add_attribute가 파싱한다 — 그래서 install 경로가 사용자 DDL과 동일해진다. dvalue_func와 row_initializer의 람다는 값으로 캡처된다 — 그래서 정의들이 catcls_init에서 catcls_install이 돌 때까지 static std::vector 안에 살아 있을 수 있다.
schema_system_catalog_builder.hpp/cpp — install 엔진.
create_and_mark_system_class (name)—sm_is_system_class/sm_is_system_vclass로 이름을 분류한 뒤db_create_class또는db_create_vclass호출, 그 후sm_mark_system_class로 결과 MOP를 시스템 소유로 표시.build_class (mop, def)— 프레임워크의 심장:smt_edit_class_mop으로SM_TEMPLATE을 열고,def.attributes를 walk하면서smt_add_attribute(또는CLASS_METHOD인 경우smt_add_class_method) 호출, default을 위해dvalue_funcinvoke,sm_update_class로 템플릿 commit, 이어서def.constraints를 walk하면서db_add_constraint호출, owner를au_change_class_owner_including_partitions으로 설정, grants를au_grant로 적용, 마지막으로def.row_initializer가 있으면 invoke.build_vclass (mop, def)— 뷰 변형:COLUMN종류는db_add_attribute,QUERY_SPEC종류는db_add_query_spec, 드물게 등장하는CLASS_METHOD는db_add_class_method(legacy 호환을 위해 보존).
schema_system_catalog_install.cpp — 데이터 파일. 두 부분.
- 정적 헬퍼 함수들(
make_int_value_fn,make_double_value_fn,make_numeric_value_fn,format_varchar,format_numeric,format_sequence)이 default-value 람다와 SQL 타입 문자열을 만들어 낸다. catcls_init(모든 정의를clist와vclist에 등록)와catcls_install(세 패스 install 실행).cubschema::system_catalog_initializer네임스페이스 —get_class(),get_attribute(),get_domain(), … 팩토리 함수를 가진 정적 클래스. 각 함수가 한 클래스의system_catalog_definition값을 반환한다. 실제 스키마가 코드 형태로 사는 곳이 여기다 — 파일의 ~50K가 이 팩토리들이다.
get_class() 발췌:
// system_catalog_initializer::get_class — schema_system_catalog_install.cpp (condensed)system_catalog_definition system_catalog_initializer::get_class () { return system_catalog_definition ( CT_CLASS_NAME, // "_db_class" { // attributes {"class_of", "object"}, // 자기-참조 MOP {"unique_name", format_varchar (255)}, {"class_name", format_varchar (255)}, {"class_type", "integer"}, {"is_system_class", "integer"}, {"owner", AU_USER_CLASS_NAME}, // "db_user" -> object // ... 25개 컬럼 더 ... }, /* constraints */ { /* (owner, unique_name) PK, class_name INDEX 등 */ }, /* auth */ { Au_dba_user, /* grants */ }, /* row_initializer */ nullptr // _db_class는 seed 데이터 없음 );}같은 모양이 24개 클래스 팩토리 모두에 적용된다. 차이점: _db_data_type은 catcls_add_data_type을 row initializer로 들고 다닌다(타입 이름 24개 행을 삽입: INTEGER, FLOAT, …). _db_charset과 _db_collation은 catcls_add_charsets / catcls_add_collations를 들고 다닌다(libcubrid_collations.so를 읽고 로딩된 로케일별로 한 행씩 삽입).
schema_system_catalog_install_query_spec.cpp — 뷰 데이터 파일. 뷰마다 sm_define_view_* 헬퍼 하나(예: sm_define_view_class_spec, sm_define_view_attribute_spec)가 컴파일 타임에 구워진 정적 SQL 문자열을 반환한다. 이 문자열들이 build_vclass의 db_add_query_spec으로 설치되는 SELECT 문이다. 파일 헤더 주석이 SQL 리터럴 형식 규칙 13개를 나열한다(들여쓰기 두 탭, 줄 끝은 공백, AND/OR 줄바꿈 등) — 규칙이 일부러 엄격한 이유는 이 파일에 대한 git blame이 다운스트림 도구에 스키마가 바뀌었다는 표준 신호이기 때문이다.
// sm_define_view_class_spec — schema_system_catalog_install_query_spec.cpp (condensed)const char *sm_define_view_class_spec (void) { static char stmt [2048]; sprintf (stmt, "SELECT " "[c].[class_name] AS [class_name], " "CAST ([c].[owner].[name] AS VARCHAR(255)) AS [owner_name], " "CASE [c].[class_type] WHEN 0 THEN 'CLASS' WHEN 1 THEN 'VCLASS' " "ELSE 'UNKNOWN' END AS [class_type], " // ... "FROM [%s] [c] " // "_db_class" "WHERE [c].[is_system_class] = 0 OR ...", CT_CLASS_NAME); return stmt;}sprintf의 %s와 CT_CLASS_NAME 매개변수화가 이 모듈을 schema_system_catalog_constants.h에 묶어 놓는다 — 클래스 이름을 바꾸려면 두 파일에서 협조된 편집이 필요하다.
위치 힌트 (2026-05-06 기준)
섹션 제목: “위치 힌트 (2026-05-06 기준)”| 심볼 | 파일 | 줄 |
|---|---|---|
catcls_init | src/object/schema_system_catalog_install.cpp | 257 |
catcls_install | src/object/schema_system_catalog_install.cpp | 315 |
system_catalog_initializer::get_class | src/object/schema_system_catalog_install.cpp | ~415 |
system_catalog_builder::create_and_mark_system_class | src/object/schema_system_catalog_builder.cpp | 33 |
system_catalog_builder::build_class | src/object/schema_system_catalog_builder.cpp | 64 |
system_catalog_builder::build_vclass | src/object/schema_system_catalog_builder.cpp | 204 |
sm_is_system_class | src/object/schema_system_catalog.cpp | 126 |
sm_system_class_names (24개 항목) | src/object/schema_system_catalog.cpp | 33 |
sm_system_vclass_names (22개 항목) | src/object/schema_system_catalog.cpp | 86 |
sm_define_view_class_spec | src/object/schema_system_catalog_install_query_spec.cpp | 62 |
Cross-check 노트
섹션 제목: “Cross-check 노트”- 정의는 컴파일 타임 상수가 아니라 런타임 값이다. 24개 클래스 정의와 22개 뷰 정의는 정적 초기화 시점이 아니라
catcls_init이 돌 때 만들어진다.install.cpp의static std::vector<catcls_function> clist선언은catcls_init의ADD_TABLE_DEFINITION매크로에 의해 런타임에 채워지고,catcls_install이 비운다. 결과:catcls_init이catcls_install보다 먼저 호출되어야 하고, 둘 다createdb부트 경로 안에서 호출되어야 한다 — 정적-초기화 단축은 없다.catcls_install끝의clist.clear ()/vclist.clear ()는 의도적이다 — 정의는 builder를 먹이려는 목적뿐이라서, 설치된 후에는 해제된다. - 프레임워크는
createdb시점에 정확히 한 번 호출된다.catcls_install은 첫 데이터베이스 생성 시boot_create_db(cubrid-boot.md)에서 호출된다. 이후 모든 재시작에서는 시스템 클래스가 이미 디스크 카탈로그에 있고, 엔진은cubrid-class-object의SM_CLASS그래프 메커니즘으로 그것을 읽어들인다 — 이 모듈은 createdb 이후 잠든다. AU_DISABLE은 옵션이 아니라 필수다. install 동안 권한 테이블(db_user,db_authorization,_db_auth) 자체가 만들어지는 중이다. 만들어지는 중인 카탈로그를 권한 검사를 돌리면 install이 멈춘다. install이AU_DISABLE로Au_dba_user로 도는 이유는 정확히, 권한 검사가 그렇지 않으면 아직 존재하지 않는 테이블을 루프를 돌기 때문이다.- 두 패스로 MOP를 만드는 것은 클래스 간 참조 때문이다.
_db_class.owner는db_user타입,_db_attribute.class_of는_db_class타입 등이다. install이 한 클래스를 완전히 빌드한 뒤에 다음 클래스로 넘어간다면, 첫 번째 클래스가 아직 존재하지 않는 타입의 attribute를 선언하려다 실패한다. Pass 1이 24개 클래스 모두를 빈 MOP를 만들어 두면 Pass 2의smt_add_attribute호출이 필요한 타입 이름을 항상 찾을 수 있다. - 뷰 query spec이 테이블 이름을
sprintf %s로 참조한다. 이건 깨지기 쉬운 부분이다 —CT_CLASS_NAME을_db_class에서 다른 무엇으로 바꾸려면 상수와, 그 테이블을 직접 이름으로 참조하는 모든 뷰 query spec을 함께 편집해야 한다.install_query_spec.cpp상단의 엄격한 형식 규칙은 일부분 그런 변경이git diff에서 잘 보이게 하기 위한 것이다. - legacy
boot_define_*함수가 아직 남아 있다.boot_sr.c에 이 모듈을 앞서던boot_define_class,boot_define_attribute같은 가족이 보존되어 있다. 더 이상 install 경로에서 호출되지는 않지만,git blame을 보면 점진적으로 deprecated되어 왔다 — 일부는 옛 도구가 여전히 참조한다.catcls_install의// new routine주석이 새 builder가 옛 직접 호출을 대체한 곳을 표시한다. row_initializer는 데이터인 seed 행이 사는 유일한 곳이다. 스키마(컬럼, constraint, grant)는 정의 구조체 안의 데이터지만, 실제 seed 행(_db_data_type의 24개 타입 이름 행,_db_collation의 로케일 행)은 row-initializer 콜백 안의 명령형 C++로 산다. 그리고 여기 초기 행들이 있다를 선언적으로 표현할 방법은 없다 — 프레임워크는 스키마에서 멈추고, 행 채우기는 람다에 넘긴다.
미해결 질문
섹션 제목: “미해결 질문”- 빌드 타임에 install 코드를 생성하는 길(Postgres BKI 방식)이 아니라 런타임을 고른 이유는? 런타임 방식은
createdb에서 일회성 비용을 내지만, 메타-스키마를 엔진의 나머지 옆에 평범한 C++로 둘 수 있다 — Perl-style 코드 생성으로는 안 되는 일이다. 트레이드오프는 실재한다 — BKI 방식은 메타-스키마를 컴파일 타임에 검증할 수 있다 — 그러나 CUBRID는 평범한 DDL 경로와의 통합을 위해 런타임을 골랐다. _db_charset/_db_collation의 자리는 다른 곳이어야 할까? 이들의 row initializer는 i18n 공유 라이브러리를 읽는다. 카탈로그 테이블이지만 내용의 소유자는cubrid-charset-collation.md와cubrid-timezone.md다(_db_timezone은 없다 — 타임존은 SHOW로만 표면화된다). 현재 자리는 스키마-부트스트랩 이야기를 통합해 두는 쪽이다.- 13개의 query-spec 형식 규칙은 부분적으로 자기-강제다.
install_query_spec.cpp모양을 돌리는 CI 린트는 없다. 규칙은 헤더 주석으로 산다. 코드 리뷰에서의 엄격한 diff가 유일한 강제다. dual과db_root는 노출된 이름이다. 대부분의 카탈로그 클래스는 선행 underscore 관습(_db_class)을 쓴다.db_user,db_password,db_authorization,db_root,dual은 그러지 않는다. 경계는 역사적이다(Oracle 호환을 위한dual, underscore 관습 이전의 권한 테이블) — 그러나 사용자에게 관찰 가능하고, 이름 prefix로 시스템 테이블을 필터링하는 클라이언트 코드를 읽을 때 머릿속에 두어야 할 사실이다.
src/object/schema_system_catalog.hpp/cpp— 최상위 API,catcls_init/catcls_install, 이름-소속 술어src/object/schema_system_catalog_builder.hpp/cpp— generic install 엔진(create_and_mark_system_class,build_class,build_vclass)src/object/schema_system_catalog_definition.hpp/cpp— 메타-스키마 구조체(attribute,constraint,grant,system_catalog_definition)src/object/schema_system_catalog_constants.h— 클래스/뷰 이름 상수src/object/schema_system_catalog_install.cpp— 24개 시스템 클래스의 정의 데이터 + 3-패스 install 오케스트레이션src/object/schema_system_catalog_install_query_spec.cpp— 22개 시스템 뷰의 SQL 리터럴 query specsrc/object/schema_manager.h,src/object/schema_template.h— builder가 호출하는 DDL 프리미티브src/transaction/boot_sr.c—boot_create_db이 createdb 시점에catcls_init+catcls_install을 한 번 호출- 인접 CUBRID 문서:
cubrid-catalog-manager.md,cubrid-class-object.md,cubrid-ddl-execution.md,cubrid-show-commands.md,cubrid-boot.md,cubrid-charset-collation.md - 텍스트북 참고: System R 1979 (Astrahan et al.); Hellerstein and Stonebraker, Anatomy of a Database System 4장; Petrov, Database Internals 1장; Selinger 1979 (Access Path Selection in a Relational DBMS)