콘텐츠로 이동

(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_classclass_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오케스트레이션이다.

네 가지 메이저 엔진은 같은 SQL 표면 카탈로그를 서로 다른 부트스트랩 메커니즘으로 만들어 낸다.

PostgreSQL은 pg_catalog 스키마 아래 pg_class, pg_attribute, pg_index, pg_constraint를 평범한 heap 테이블로 둔다. 부트스트랩은 genbki.pl이 담당한다. 이 Perl 스크립트가 src/include/catalog/pg_*.hBKI_* 매크로 주석이 달린 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로 들여다 볼 수 있다.

모듈은 데이터 주도 프레임워크를 함께 구현하는 네 조각으로 갈라진다.

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 디스크리미네이터가 경로를 가른다. COLUMNCLASS_METHODsmt_add_attribute / smt_add_class_method로 가고, QUERY_SPECdb_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.ownerdb_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_codePL 패밀리저장 프로시저 레지스트리(소비처는 cubrid-pl-javasp.md, cubrid-pl-plcsql.md).
_db_ha_apply_info복제로그-applier 체크포인트 상태.
_db_collation, _db_charseti18n플랫폼별 libcubrid_collations.so에서 로딩됨(cubrid-charset-collation.md).
_db_serverfederationDBLink 서버 카탈로그.
_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_domainset-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_authclass_of/grantor/grantee MOP를 읽기 좋은 이름으로 풀어냄.
db_trigger, db_partitiontrigger / partition카탈로그 플래그를 사람이 읽을 수 있는 enum으로 디코딩.
db_stored_procedure, db_stored_procedure_argsSP 가족사용자가 \sp로 보는 시그니처.
db_serial, db_ha_apply_info기능 뷰캐시된 상태.
db_collation, db_charseti18n 뷰UTF-8/ICU 디코딩됨.
db_server, db_synonymfederation 뷰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-bootcreatedb 동안 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_funcrow_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_func invoke, 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_METHODdb_add_class_method(legacy 호환을 위해 보존).

schema_system_catalog_install.cpp — 데이터 파일. 두 부분.

  1. 정적 헬퍼 함수들(make_int_value_fn, make_double_value_fn, make_numeric_value_fn, format_varchar, format_numeric, format_sequence)이 default-value 람다와 SQL 타입 문자열을 만들어 낸다.
  2. catcls_init(모든 정의를 clistvclist에 등록)와 catcls_install(세 패스 install 실행).
  3. 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_typecatcls_add_data_type을 row initializer로 들고 다닌다(타입 이름 24개 행을 삽입: INTEGER, FLOAT, …). _db_charset_db_collationcatcls_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_vclassdb_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%sCT_CLASS_NAME 매개변수화가 이 모듈을 schema_system_catalog_constants.h에 묶어 놓는다 — 클래스 이름을 바꾸려면 두 파일에서 협조된 편집이 필요하다.

심볼파일
catcls_initsrc/object/schema_system_catalog_install.cpp257
catcls_installsrc/object/schema_system_catalog_install.cpp315
system_catalog_initializer::get_classsrc/object/schema_system_catalog_install.cpp~415
system_catalog_builder::create_and_mark_system_classsrc/object/schema_system_catalog_builder.cpp33
system_catalog_builder::build_classsrc/object/schema_system_catalog_builder.cpp64
system_catalog_builder::build_vclasssrc/object/schema_system_catalog_builder.cpp204
sm_is_system_classsrc/object/schema_system_catalog.cpp126
sm_system_class_names (24개 항목)src/object/schema_system_catalog.cpp33
sm_system_vclass_names (22개 항목)src/object/schema_system_catalog.cpp86
sm_define_view_class_specsrc/object/schema_system_catalog_install_query_spec.cpp62
  • 정의는 컴파일 타임 상수가 아니라 런타임 값이다. 24개 클래스 정의와 22개 뷰 정의는 정적 초기화 시점이 아니라 catcls_init이 돌 때 만들어진다. install.cppstatic std::vector<catcls_function> clist 선언은 catcls_initADD_TABLE_DEFINITION 매크로에 의해 런타임에 채워지고, catcls_install이 비운다. 결과: catcls_initcatcls_install보다 먼저 호출되어야 하고, 둘 다 createdb 부트 경로 안에서 호출되어야 한다 — 정적-초기화 단축은 없다. catcls_install 끝의 clist.clear () / vclist.clear ()는 의도적이다 — 정의는 builder를 먹이려는 목적뿐이라서, 설치된 후에는 해제된다.
  • 프레임워크는 createdb 시점에 정확히 한 번 호출된다. catcls_install은 첫 데이터베이스 생성 시 boot_create_db(cubrid-boot.md)에서 호출된다. 이후 모든 재시작에서는 시스템 클래스가 이미 디스크 카탈로그에 있고, 엔진은 cubrid-class-objectSM_CLASS 그래프 메커니즘으로 그것을 읽어들인다 — 이 모듈은 createdb 이후 잠든다.
  • AU_DISABLE은 옵션이 아니라 필수다. install 동안 권한 테이블(db_user, db_authorization, _db_auth) 자체가 만들어지는 중이다. 만들어지는 중인 카탈로그를 권한 검사를 돌리면 install이 멈춘다. install이 AU_DISABLEAu_dba_user로 도는 이유는 정확히, 권한 검사가 그렇지 않으면 아직 존재하지 않는 테이블을 루프를 돌기 때문이다.
  • 두 패스로 MOP를 만드는 것은 클래스 간 참조 때문이다. _db_class.ownerdb_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.mdcubrid-timezone.md다(_db_timezone은 없다 — 타임존은 SHOW로만 표면화된다). 현재 자리는 스키마-부트스트랩 이야기를 통합해 두는 쪽이다.
  • 13개의 query-spec 형식 규칙은 부분적으로 자기-강제다. install_query_spec.cpp 모양을 돌리는 CI 린트는 없다. 규칙은 헤더 주석으로 산다. 코드 리뷰에서의 엄격한 diff가 유일한 강제다.
  • dualdb_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 spec
  • src/object/schema_manager.h, src/object/schema_template.h — builder가 호출하는 DDL 프리미티브
  • src/transaction/boot_sr.cboot_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)