CUBRID DDL Execution — Schema-Change Pipeline From Parse Tree to Catalog and Class-Object Cache Invalidation
Contents:
- Theoretical Background
- Common DBMS Design
- CUBRID’s Approach
- Source Walkthrough
- Cross-check Notes
- Open Questions
- Sources
Theoretical Background
Section titled “Theoretical Background”The Data-Definition Language is the part of SQL that does not move
rows; it moves the shape under which rows are interpreted. SQL’s
DDL statements — CREATE TABLE, ALTER TABLE, DROP TABLE,
CREATE INDEX, RENAME, TRUNCATE and their object-relational and
partitioning extensions — together build the schema graph from
which every later SELECT/INSERT/UPDATE plan is compiled. Database
System Concepts (Silberschatz et al., 7th ed., §5.2 and §15.5)
captures the textbook structure of any DDL implementation in five
movements: parse the DDL into a definition tree, validate it
against the existing catalog, materialise the on-disk artefacts
(heap, B-trees, foreign-key descriptors), insert/update/delete the
matching catalog rows, and broadcast invalidation so caches that
referenced the old shape are flushed before the next plan compile.
Several design decisions are forced by what kind of DBMS the engine is.
-
Transactional vs. non-transactional DDL. SQL Server, DB2, Postgres and MySQL 8.0 wrap DDL in the same WAL/recovery framework as DML —
CREATE TABLE+ROLLBACKis identical to noCREATE TABLE. Older MySQL releases and Oracle treat DDL as an implicit commit. A transactional engine must journal every catalog mutation through the same redo/undo log as user data; an implicit-commit engine only needs the catalog row to land eventually. -
Schema-change vs. data movement. Pure-metadata DDL (
CREATE TABLE,DROP INDEX) takes a short exclusive lock and exits. Data-touching DDL (ALTER … ADD COLUMN c NOT NULL DEFAULT 0, partition reorg,TRUNCATE) needs a cursor-style scan that can run for hours under the same long DDL. -
DDL fence and cache invalidation. Every cached plan referencing a changed class must be invalidated before a new plan compiles against the new shape. The standard discipline is a monotonically increasing schema version: Oracle’s Library Cache Lock, Postgres’
CommandCounterIncrement+ relcache invalidation messages, CUBRID’ssm_bump_local_schema_versionplus a per-classchnon each MOP. -
Staging. Mutating the live class would expose partial states to concurrent readers. The standard answer is an in-memory template the engine mutates freely and swaps in atomically at end-of-statement under exclusive lock. Postgres rewrites a
Relationin place underAccessExclusiveLock; InnoDB has online DDL with a ghost copy + row-log; Oracle usesDBMS_REDEFINITION. CUBRID’s answer is theSM_TEMPLATE— a working copy ofSM_CLASSinside aDB_CTMPLhandle, installed bysm_finish_class → install_new_representation. -
Catalog representation. Tuple-style engines (Postgres
pg_class/pg_attribute, MySQL 8.0mysql.tables, Oracledba_objects) hold one row per object and treat DDL as an INSERT/UPDATE/DELETE over privileged system tables. Object-style engines treat each class as an instance of a meta-class (_db_class,_db_attribute); CUBRID is the second style — the catalog is the set of_db_-prefixed classes andcatcls_*insrc/storage/catalog_class.ctranslates betweenSM_CLASSand the heap-row representation.
The book chapters that motivate this layering are Database System Concepts §5.2 (DDL syntax), §15.5 (System Catalog), and §17 (Recovery and the role of WAL for DDL). Petrov’s Database Internals (Ch. 4) frames the same machinery from the perspective of a single-node engine: schema state, when written to disk through the same B-tree that holds tuples, automatically inherits durability, locking, and crash recovery.
Common DBMS Design
Section titled “Common DBMS Design”Postgres’ DDL pipeline is a useful baseline. A CREATE TABLE
parses to a CreateStmt node; the ProcessUtility switch in
src/backend/tcop/utility.c dispatches to
DefineRelation. DefineRelation builds a TupleDesc
(equivalent in spirit to an SM_TEMPLATE), calls
heap_create_with_catalog to allocate the relfilenode, the
pg_class row, and the pg_attribute rows, then registers
relevant constraints. Two further moves wire up the cache: every
catalog INSERT goes through CatalogTupleInsert, which records an
invalidation message for the relfilenode and the relid;
CommandCounterIncrement at end-of-statement makes that
invalidation visible to subsequent statements in the same
transaction. The relcache rebuilds lazily on next access. DDL
is fully transactional: a ROLLBACK undoes both the catalog rows
and the file-system operations through pendingDeletes.
MySQL 8.0’s atomic DDL drove a complete rewrite of the data
dictionary: every DDL is a single InnoDB transaction over the new
mysql.tables / mysql.columns system tables, with the
file-system operations queued through a DDL log so that crash
recovery either commits the entire DDL or rolls it back. Earlier
MySQL versions famously did not — CREATE TABLE could leave the
.frm file behind even on rollback. The lesson is that storage
engine and dictionary must share a transactional substrate; if
they do not, atomic DDL is unreachable.
Oracle takes the opposite route: every DDL is bracketed by an
implicit commit. CREATE TABLE first commits any pending user
transaction, then runs in its own transaction over the data
dictionary, then commits again. The shared cursor cache uses the
library cache lock (a hash partition latch) to fence cached
plans: the DDL takes the lock in exclusive mode, marks dependent
cursors invalid, and releases the lock; subsequent compiles take
the lock in shared mode and re-parse if any dependency was marked.
SQL Server and DB2 are similar in spirit to Postgres: catalog rows live in regular system tables, DDL is fully transactional, and a per-database “schema version” plus per-statement recompilation handles cache invalidation.
CUBRID combines several traits from this menu. Like Postgres and
MySQL 8.0 it is fully transactional: every DDL acquires a
system savepoint at entry and rolls back to that savepoint on any
error path, so a partial ALTER TABLE cannot leak. Like Oracle
it stages the new shape in an in-memory template before
committing; CUBRID’s SM_TEMPLATE is the analogue of Oracle’s
work area inside the data-dictionary cache, but it lives client-
side in the workspace rather than inside a shared library cache.
Like Postgres, it bumps a monotonically increasing version number
to invalidate plan caches; sm_bump_local_schema_version is the
direct analogue of CommandCounterIncrement. The catalog is
written through catcls_* rather than through plain
heap_insert because CUBRID’s catalog is a set of system
classes rather than ordinary tables; an SM_CLASS becomes a
heap row in _db_class only after the template has been
finalised.
The walkthrough that follows will show how these pieces are arranged into a single pipeline, where each CUBRID-specific choice — workspace MOPs, locator-mediated heap creation, two-phase partitioning — fits in.
CUBRID’s Approach
Section titled “CUBRID’s Approach”flowchart TB SQL[SQL text] SQL --> Parse[csql_grammar.y<br/>PT_CREATE_ENTITY / PT_ALTER / PT_DROP] Parse --> Sem[name_resolution<br/>· semantic_check] Sem --> DoStmt[do_statement / do_execute_statement<br/>switch on PT_NODE_TYPE] DoStmt --> CE[do_create_entity] DoStmt --> AL[do_alter] DoStmt --> DR[do_drop] DoStmt --> CI[do_create_index] DoStmt --> TR[do_create_trigger] DoStmt --> SE[do_create_serial] DoStmt --> US[do_create_user / do_grant] CE --> Tmpl[dbt_create_class<br/>= smt_def_class] AL --> Tmpl2[dbt_edit_class<br/>= smt_edit_class_mop] Tmpl --> Local[do_create_local<br/>do_add_attributes / do_add_constraints] Tmpl2 --> Local Local --> Finish[dbt_finish_class<br/>= sm_finish_class -> update_class] Finish --> Install[install_new_representation<br/>· allocate_disk_structures] Install --> Locator[locator_add_class<br/>locator_create_heap_if_needed] Locator --> Catcls[catcls_insert_catalog_classes<br/>via locator flush] Catcls --> Bump[sm_bump_local_schema_version<br/>· XASL cache invalidation on next access] DR --> DropPath[db_drop_class_ex<br/>-> sm_delete_class_mop] DropPath --> Locator
The pipeline is the spine of the DDL implementation: every concrete DDL statement is a specialisation of the same path. The remainder of this section walks each phase in detail, with code excerpts preserved verbatim so the reader does not have to chase the source tree.
Top-level dispatch — do_statement and the DDL switch
Section titled “Top-level dispatch — do_statement and the DDL switch”The single-statement entry point is do_statement in
execute_statement.c. It is called once per parsed
PT_NODE. The function does three things in order: assess the
read-fetch instance version (DDL needs LC_FETCH_DIRTY_VERSION
because we are about to write the catalog), then a giant
switch (statement->node_type) that routes each parse-tree kind
to its handler, then a tail that triggers replication and
supplemental log capture for HA.
// do_statement — execute_statement.c (DDL slice of the switch)case PT_CREATE_ENTITY: error = do_check_internal_statements (parser, statement, do_create_entity); break;case PT_ALTER: error = do_check_internal_statements (parser, statement, do_alter); break;case PT_DROP: (void) do_reserve_classinfo (parser, statement, cls_info); error = do_check_internal_statements (parser, statement, do_drop); break;case PT_CREATE_INDEX: error = do_create_index (parser, statement); break;case PT_DROP_INDEX: error = do_drop_index (parser, statement); break;case PT_ALTER_INDEX: error = do_alter_index (parser, statement); break;case PT_RENAME: error = do_rename (parser, statement); break;case PT_TRUNCATE: error = do_truncate (parser, statement); break;case PT_CREATE_TRIGGER: error = do_create_trigger (parser, statement); break;case PT_DROP_TRIGGER: error = do_drop_trigger (parser, statement); break;case PT_ALTER_TRIGGER: error = do_alter_trigger (parser, statement); break;case PT_CREATE_SERIAL: error = do_create_serial (parser, statement); break;case PT_ALTER_SERIAL: error = do_alter_serial (parser, statement); break;case PT_DROP_SERIAL: error = do_drop_serial (parser, statement); break;case PT_CREATE_USER: error = do_create_user (parser, statement); break;case PT_DROP_USER: error = do_drop_user (parser, statement); break;case PT_GRANT: error = do_grant (parser, statement); break;case PT_REVOKE: error = do_revoke (parser, statement); break;case PT_CREATE_STORED_PROCEDURE: error = jsp_create_stored_procedure (parser, statement); break;The wrapper do_check_internal_statements is currently a thin
pass-through to do_func (parser, statement) — it exists to host
text-domain internal statements that are presently disabled
behind #if 0. The mental model is “this dispatch line is the
DDL switch”, with the wrapper a vestigial hook for
auto-generated companion statements.
After the handler returns, the same do_statement body wires the
DDL into HA replication:
// do_statement — execute_statement.c (post-handler tail)if (need_stmt_replication) { int repl_error = NO_ERROR; if (error >= 0) { repl_error = locator_all_flush (); } suppress_repl_error = db_set_suppress_repl_on_transaction (false); if (error >= 0 && repl_error == NO_ERROR && suppress_repl_error == NO_ERROR) { repl_error = do_replicate_statement (parser, statement); } /* ... */ }
if (prm_get_integer_value (PRM_ID_SUPPLEMENTAL_LOG) > 0) { (void) do_supplemental_statement (parser, statement, cls_info, reserved_oid); }locator_all_flush () is the critical call: any transient
workspace MOPs created during the DDL get flushed to disk
before the schema-replication record is written, otherwise the
log applier on the slave would see a catalog row pointing at a
class with no heap file.
CREATE TABLE — building the SM_TEMPLATE end-to-end
Section titled “CREATE TABLE — building the SM_TEMPLATE end-to-end”A CREATE TABLE parse-tree node carries entity_type = PT_CLASS, the name, the column list, the constraint list,
optional LIKE source class, optional AS SELECT query,
optional partition info, and table options (charset,
collation, comment, REUSE_OID, ENCRYPT). The handler
do_create_entity (execute_schema.c) follows a fixed
sequence: take a system savepoint, build a template, populate
it via do_create_local, finish the template, then run the
post-create steps.
sequenceDiagram
participant Parser
participant DoCE as do_create_entity
participant Smt as smt_def_class<br/>(schema_template.c)
participant Loc as locator_reserve_class_name<br/>(locator_cl.c)
participant DCL as do_create_local
participant SmF as sm_finish_class<br/>(schema_manager.c)
participant SmU as update_class<br/>install_new_representation
participant LAdd as locator_add_class
participant Heap as locator_create_heap_if_needed<br/>· heap_create
participant Cat as catcls_insert_catalog_classes<br/>(catalog_class.c)
Parser->>DoCE: PT_NODE { create_entity }
DoCE->>DoCE: tran_system_savepoint("cREATEeNTITY")
DoCE->>Smt: dbt_create_class(name)
Smt->>Loc: locator_reserve_class_name(name)
Loc-->>Smt: pseudo-OID, SCH_M_LOCK
Smt-->>DoCE: SM_TEMPLATE
DoCE->>DCL: do_create_local(parser, ctemplate, node)
DCL->>DCL: do_add_attributes
DCL->>DCL: do_add_constraints
DCL->>DCL: do_check_fk_constraints
DCL-->>DoCE: NO_ERROR
DoCE->>SmF: dbt_finish_class(ctemplate)
SmF->>SmU: update_class(template, &classmop, ...)
SmU->>SmU: lockhint_subclasses<br/>flatten_template
SmU->>LAdd: locator_add_class(SM_CLASS, name)
LAdd-->>SmU: classmop (cached in workspace)
SmU->>SmU: install_new_representation
SmU->>SmU: allocate_disk_structures (B-trees)
SmU-->>DoCE: classmop
DoCE->>Heap: locator_create_heap_if_needed(class_obj, reuse_oid)
Heap->>Heap: heap_create(hfid, oid, reuse_oid)
Heap-->>DoCE: hfid set on SM_CLASS
DoCE->>DoCE: do_create_partition (if partitioned)
DoCE->>DoCE: do_create_index (per CREATE INDEX clause)
DoCE->>DoCE: locator_flush_class -> Cat
Cat-->>DoCE: row in _db_class
DoCE-->>Parser: NO_ERROR
The savepoint at do_create_entity head is named
UNIQUE_SAVEPOINT_CREATE_ENTITY = "cREATEeNTITY" and is the
only thing that makes a CREATE TABLE failure recoverable: if
any later step fails, tran_abort_upto_system_savepoint rolls
the entire DDL back to before the catalog mutation.
// do_create_entity — execute_schema.c (skeleton)class_name = node->info.create_entity.entity_name->info.name.original;/* ... super-class partitioning checks, table option parsing ... */
error = tran_system_savepoint (UNIQUE_SAVEPOINT_CREATE_ENTITY);do_rollback_on_error = true;
ctemplate = create_like ? dbt_copy_class (class_name, create_like, &source_class) : dbt_create_class (class_name);if (!create_like) error = do_create_local (parser, ctemplate, node, query_columns);
class_obj = dbt_finish_class (ctemplate);
if (locator_create_heap_if_needed (class_obj, reuse_oid) == NULL) { error = er_errid (); break; }
if (node->info.create_entity.partition_info != NULL) error = do_create_partition (parser, node, &info);
for (create_index = node->info.create_entity.create_index; create_index; create_index = create_index->next) error = do_create_index (parser, create_index);dbt_create_class is in src/compat/db_temp.c. It is a thin
wrapper around smt_def_class that also reserves the class
name with the server through locator_reserve_class_name:
// dbt_create_class + dbt_reserve_name — db_temp.c (condensed)def = smt_def_class (name); /* allocate SM_TEMPLATE */if (def != NULL) def = dbt_reserve_name (def, name);return def;
// dbt_reserve_name:reserved = locator_reserve_class_name (def->name, &class_oid); /* server hash insert */if (reserved != LC_CLASSNAME_RESERVED) { /* LC_CLASSNAME_EXIST -> ER_LC_CLASSNAME_EXIST */ smt_quit (def); return NULL; }return def;Reserving the name before the template is populated lets two
concurrent CREATE TABLE statements race for the name and see a
deterministic loser — the second one gets LC_CLASSNAME_EXIST
and aborts, regardless of which one actually finishes the
template first. This is CUBRID’s analogue of Postgres’
namespace-level lock taken in RangeVarGetAndCheckCreationNamespace.
do_create_local is the column/constraint plumbing; it is a
straight sequence of do_add_* helpers, each of which mutates
the template:
// do_create_local — execute_schema.c (template-population sequence)error = do_add_attributes (parser, ctemplate, attr_def_list, constraint_list, query_columns);error = do_add_attributes (parser, ctemplate, class_attr_def_list, NULL, NULL);error = do_add_constraints (ctemplate, constraint_list);error = do_check_fk_constraints (ctemplate, constraint_list);error = do_add_methods (parser, ctemplate, method_def_list);error = do_add_method_files (parser, ctemplate, method_file_list);error = do_add_resolutions (parser, ctemplate, resolution_list);error = do_add_supers (parser, ctemplate, supclass_list);error = do_add_queries (parser, ctemplate, as_query_list);error = do_set_object_id (parser, ctemplate, object_id_list);The result is a fully populated SM_TEMPLATE that only lives
in the executor’s workspace. The catalog has not been touched
yet; the only thing on the server side is the reserved name
entry from locator_reserve_class_name.
dbt_finish_class calls sm_finish_class, which in turn calls
update_class — the central template-installation routine.
// sm_finish_class — schema_manager.cintsm_finish_class (SM_TEMPLATE * template_, MOP * classmop){ return update_class (template_, classmop, 0, AU_ALTER, true);}update_class is the heart of the DDL pipeline. It does, in
order: bump the local schema version, fetch the existing
SM_CLASS if this is an ALTER (NULL if CREATE), pre-lock the
subclass lattice and the super-classes, flatten the template
(merging inherited components), lock the subclasses for write,
flatten subclasses, allocate the persistent class object via
locator_add_class if new, install the new representation,
allocate the disk B-trees, and finally update the super- and
sub-class lists.
// update_class — schema_manager.c (condensed)sm_bump_local_schema_version ();error = tran_system_savepoint (SM_ADD_UNIQUE_CONSTRAINT_SAVEPOINT_NAME);if ((error == NO_ERROR) && (template_->op != NULL)) error = au_fetch_class (template_->op, &class_, AU_FETCH_UPDATE, auth);
if (needs_hierarchy_lock) { error = lockhint_subclasses (template_, class_); error = lock_supers (template_, class_ ? class_->inheritance : NULL, &oldsupers, &newsupers); }
if (class_ != NULL) class_->new_ = template_;error = flatten_template (template_, NULL, &flat, auto_res);if (needs_hierarchy_lock) error = lock_subclasses (template_, newsupers, class_ ? class_->users : NULL, &newsubs);class_->new_ = flat;error = flatten_subclasses (newsubs, NULL);
if (class_ == NULL) /* fresh class -> new MOP */ { class_ = classobj_make_class (template_->name); /* ... owner assignment ... */ template_->op = locator_add_class ((MOBJ) class_, sm_ch_name ((MOBJ) class_)); }
flat->partition_parent_atts = template_->partition_parent_atts;error = install_new_representation (template_->op, class_, flat);num_indexes = allocate_disk_structures (template_->op, class_, newsubs, template_);error = update_supers (template_->op, oldsupers, newsupers);error = update_subclasses (newsubs);The single line template_->op = locator_add_class (...) is
when a fresh SM_CLASS becomes a workspace MOP — the
client-side handle for the class object — bound to the
temporary OID returned by locator_reserve_class_name. The
permanent OID is assigned at flush time by the server.
install_new_representation is the structural mutation step:
it fixes self-referential domains, runs build_storage_order
to assign attribute IDs, and decides whether the change forces
a new on-disk representation (a CUBRID-specific concept: an
SM_CLASS may carry many historical representations so old
heap rows can still be parsed). If it does, every cached
instance of the class is flushed and decached.
// install_new_representation — schema_manager.c (condensed)fixup_component_classes (classop, flat);fixup_self_reference_domains (classop, flat);check_inherited_attributes (classop, class_, flat);needrep = build_storage_order (class_, flat);for (a = flat->shared_attributes; a != NULL; a = a->header.next) assign_attribute_id (class_, a, 0);for (a = flat->class_attributes; a != NULL; a = a->header.next) assign_attribute_id (class_, a, 1);
if (needrep && !classop->no_objects) { locator_flush_all_instances (classop, DECACHE); locator_update_class (classop); newrep = 1; WS_SET_NO_OBJECTS (classop); }
error = transfer_disk_structures (classop, class_, flat);invalidate_unused_triggers (classop, class_, flat);sm_reset_descriptors (classop);error = classobj_install_template (class_, flat, newrep);locator_update_class (classop);The two pieces worth noting here are
transfer_disk_structures, which reconciles the old
SM_CLASS_CONSTRAINT list with the new flat one and either
preserves or deallocate_index-es each B-tree, and
classobj_install_template, which does the actual swap: it
drops the old SM_CLASS field-by-field and copies the flattened
template into the live class object. The class MOP is now
authoritative; the next DML compile that fetches it will see the
new shape.
Back in do_create_entity, after dbt_finish_class returns,
the heap is created lazily — heap allocation is deferred to
locator_create_heap_if_needed so a vclass (view) can finish
without ever paying a heap, and a regular class only pays once.
// locator_create_heap_if_needed — locator_cl.c (condensed)class_obj = locator_fetch_class (class_mop, DB_FETCH_CLREAD_INSTWRITE);hfid = sm_ch_heap (class_obj);if (HFID_IS_NULL (hfid)) { class_obj = locator_fetch_class (class_mop, DB_FETCH_WRITE); oid = ws_oid (class_mop); if (OID_ISTEMP (oid)) { locator_flush_class (class_mop); oid = ws_oid (class_mop); } heap_create (hfid, oid, reuse_oid); /* lob processing, dirty, flush */ }If the OID is still temporary (the class has never been flushed
to the server), locator_flush_class is invoked first; this is
what reifies the temporary OID into a permanent one and triggers
catcls_insert_catalog_classes server-side. A CREATE TABLE
therefore typically performs the catalog write here, not at
the end of the statement.
Finally, partitioning and same-statement CREATE INDEX clauses
are dispatched. Inline CREATE TABLE … AS SELECT is desugared
into an INSERT INTO target SELECT … at the bottom of
do_create_entity and re-entered through do_statement — a
small but elegant trick that lets the engine reuse all of
DML.
ALTER TABLE — multi-clause and single-clause paths
Section titled “ALTER TABLE — multi-clause and single-clause paths”do_alter walks the chain (ALTER may carry several clauses)
and executes each under a single UNIQUE_SAVEPOINT_MULTIPLE_ALTER
savepoint so an error in clause N rolls back clauses 1..N-1.
stateDiagram-v2
[*] --> Savepoint: tran_system_savepoint("mULTIPLEaLTER")
Savepoint --> Loop: for each crt_clause
Loop --> SemCheck: pt_compile (re-check 2nd+ clause)
SemCheck --> Code: switch(alter_code)
Code --> Rename: PT_RENAME_ENTITY
Code --> AddIdx: PT_ADD_INDEX_CLAUSE
Code --> DropIdx: PT_DROP_INDEX_CLAUSE
Code --> ChgAI: PT_CHANGE_AUTO_INCREMENT
Code --> ChgAttr: PT_CHANGE_ATTR
Code --> Owner: PT_CHANGE_OWNER
Code --> Coll: PT_CHANGE_COLLATION
Code --> TblComm: PT_CHANGE_TABLE_COMMENT
Code --> ColComm: PT_CHANGE_COLUMN_COMMENT
Code --> WithTmpl: default -> do_alter_one_clause_with_template
Rename --> Loop
AddIdx --> Loop
DropIdx --> Loop
ChgAI --> Loop
ChgAttr --> Loop
Owner --> Loop
Coll --> Loop
TblComm --> Loop
ColComm --> Loop
WithTmpl --> Loop
Loop --> [*]: NO_ERROR
Loop --> Rollback: error -> tran_abort_upto_system_savepoint
Rollback --> [*]
// do_alter — execute_schema.c (condensed)error_code = tran_system_savepoint (UNIQUE_SAVEPOINT_MULTIPLE_ALTER);for (crt_clause = alter; crt_clause != NULL; crt_clause = crt_clause->next) { if (do_semantic_checks) { /* re-pt_compile 2nd+ clauses against the freshly mutated class */ } switch (crt_clause->info.alter.code) { case PT_RENAME_ENTITY: error_code = do_alter_clause_rename_entity (parser, crt_clause); break; case PT_ADD_INDEX_CLAUSE: error_code = do_alter_clause_add_index (parser, crt_clause); break; case PT_DROP_INDEX_CLAUSE: error_code = do_alter_clause_drop_index (parser, crt_clause); break; case PT_CHANGE_AUTO_INCREMENT: error_code = do_alter_change_auto_increment (parser, crt_clause); break; case PT_CHANGE_ATTR: error_code = do_alter_clause_change_attribute (parser, crt_clause); break; case PT_CHANGE_OWNER: error_code = do_alter_change_owner (parser, crt_clause); break; case PT_CHANGE_COLLATION: error_code = do_alter_change_default_cs_coll (parser, crt_clause); break; case PT_CHANGE_TABLE_COMMENT: error_code = do_alter_change_tbl_comment (parser, crt_clause); break; case PT_CHANGE_COLUMN_COMMENT: error_code = do_alter_change_col_comment (parser, crt_clause); break; default: error_code = do_alter_one_clause_with_template (parser, crt_clause); /* template-mutating clauses */ } if (error_code != NO_ERROR) goto error_exit; do_semantic_checks = true; }The catch-all do_alter_one_clause_with_template handles the
clauses that mutate the SM_TEMPLATE rather than poke at the live
SM_CLASS: PT_ADD_QUERY, PT_DROP_QUERY, PT_MODIFY_QUERY,
PT_ADD_ATTR_MTHD, PT_DROP_ATTR_MTHD, PT_MODIFY_ATTR_MTHD,
PT_RESET_QUERY, plus all partition-altering codes
(PT_APPLY_PARTITION, PT_REMOVE_PARTITION, PT_ADD_PARTITION,
PT_ADD_HASHPARTITION, PT_REORG_PARTITION,
PT_COALESCE_PARTITION, PT_ANALYZE_PARTITION,
PT_PROMOTE_PARTITION).
The skeleton of every clause that touches the template is the
same: edit, mutate, finish, edit-again to add constraints if
needed, finish-again. The PT_ADD_ATTR_MTHD case is the most
illustrative because it shows the typical two-finish pattern
that arises whenever you add a column and a unique constraint
in one statement:
// do_alter_one_clause_with_template / PT_ADD_ATTR_MTHD — execute_schema.c (condensed)error = tran_system_savepoint (UNIQUE_SAVEPOINT_ADD_ATTR_MTHD);error = do_add_attributes (parser, ctemplate, attr_def_list, constraint_list, NULL);vclass = dbt_finish_class (ctemplate); /* first finish: column added */ctemplate = dbt_edit_class (vclass); /* re-edit so constraints can reference new column */error = do_add_constraints (ctemplate, constraint_list);error = do_check_fk_constraints (ctemplate, constraint_list);if (mthd_def_list != NULL) error = do_add_methods (parser, ctemplate, mthd_def_list);/* second dbt_finish_class happens in the common tail */The reason for the split is that constraints can reference the new column, but the column does not exist on the server until the template is finished and the catalog row is written. So the column is committed, the class is re-edited with a fresh template that includes the new column, and the constraint is attached.
The PT_CHANGE_ATTR path (do_alter_clause_change_attribute)
is even more elaborate because changing an attribute may require
an instance-level update (do_run_update_query_for_class,
do_run_upgrade_instances_domain) when the new domain is not a
trivial superset of the old.
DROP TABLE — sm_delete_class_mop and the cascade
Section titled “DROP TABLE — sm_delete_class_mop and the cascade”do_drop is shorter: per entity in the drop list, partition
sanity check, then drop_class_name → db_drop_class_ex → sm_delete_class_mop.
// do_drop / drop_class_name — execute_schema.cerror = tran_system_savepoint (UNIQUE_SAVEPOINT_DROP_ENTITY);
for (entity_spec = entity_spec_list; entity_spec; entity_spec = entity_spec->next) for (entity = entity_spec->info.spec.flat_entity_list; entity; entity = entity->next) { error = drop_class_name (entity->info.name.original, statement->info.drop.is_cascade_constraints); if (error != NO_ERROR) goto error_exit; }
/* drop_class_name */class_mop = db_find_class (name);if (class_mop) return db_drop_class_ex (class_mop, is_cascade_constraints);sm_delete_class_mop is the inverse of the CREATE pipeline.
A partitioned class branches out to do_drop_partitioned_class
which recurs into each partition; the steady-state path runs
tran_system_savepoint (SM_DROP_CLASS_MOP_SAVEPOINT_NAME),
sm_bump_local_schema_version, au_fetch_class (..., AU_FETCH_WRITE, AU_ALTER), lockhint_subclasses, an FK referrers check
(cascade-drop or ER_FK_CANT_DROP_PK_REFERRED), removal of any
auto-increment SERIAL objects, then classobj_make_template (NULL, op, class_) to produce a null template that represents the
disappearance of the class. Sub/super-class locks are taken via
lock_supers_drop / lock_subclasses / flatten_subclasses;
every cached instance is ws_mark_instances_deleted-ed and
flushed with DECACHE before the heap is destroyed (otherwise
the workspace would dereference freed memory); update_supers_drop
and update_subclasses reshape the schema graph;
transfer_disk_structures (op, class_, NULL) deallocates every
B-tree (the flat = NULL is what tells it to drop, not migrate);
remove_class_triggers performs physical trigger deletion. The
catalog row is deleted server-side at flush time by
catcls_delete_catalog_classes under the same MVCC transaction.
A successful DROP TABLE therefore looks like a series of
client-side mutations that converge at flush time on a catalog
delete plus a heap destroy.
Index DDL — create_or_drop_index_helper
Section titled “Index DDL — create_or_drop_index_helper”do_create_index (PT_CREATE_INDEX) and do_drop_index
(PT_DROP_INDEX) both delegate to a shared helper:
// create_or_drop_index_helper — execute_schema.c (signature)static intcreate_or_drop_index_helper (PARSER_CONTEXT *parser, const char *const constraint_name, const bool is_reverse, const bool is_unique, const PT_INDEX_INFO *idx_info, DB_OBJECT *const obj, DO_INDEX do_index);The helper rejects index DDL on a partition; computes
DB_CONSTRAINT_TYPE from (is_reverse, is_unique); materialises
filter predicates (pt_to_pred_with_context,
xts_map_filter_pred_to_stream) and function-index expressions
(pt_node_to_function_index) into the streamed
SM_PREDICATE_INFO and SM_FUNCTION_INFO; derives the canonical
name through sm_produce_constraint_name; and finally calls
sm_add_constraint (which allocates the B-tree via
allocate_disk_structures) or sm_drop_constraint (which frees
it).
PT_ALTER_INDEX splits through do_alter_index into
do_alter_index_rebuild (drop+create), do_alter_index_rename,
do_alter_index_comment, and the VISIBLE/INVISIBLE toggle
do_alter_index_status.
Partition DDL — pre/post split and child-class fanout
Section titled “Partition DDL — pre/post split and child-class fanout”CUBRID’s partition DDL is implemented as a root-template
mutation on the parent class plus a fanout of do_create_local
calls for each child partition. The driver
do_alter_partitioning_pre runs before the parent template is
finished, and do_alter_partitioning_post runs after, so that
the partition columns and constraints can be in place before the
children are materialised.
// do_alter_partitioning_pre — execute_schema.cswitch (alter_op) { case PT_APPLY_PARTITION: /* set SM_ATTFLAG_PARTITION_KEY on parent template */ case PT_ADD_PARTITION: case PT_ADD_HASHPARTITION: error = do_create_partition (parser, alter, pinfo); break; case PT_REMOVE_PARTITION: error = do_remove_partition_pre (parser, alter, pinfo); break; case PT_COALESCE_PARTITION: error = do_coalesce_partition_pre (parser, alter, pinfo); break; case PT_REORG_PARTITION: error = do_reorganize_partition_pre (parser, alter, pinfo); break; /* ... */ }do_create_partition itself synthesises a PT_CREATE_ENTITY
parse tree per child partition, naming them with the
PARTITIONED_SUB_CLASS_TAG (__p__) suffix, and recurses
through do_create_local for each. The child template inherits
the parent (supclass_list) and carries a partition field
populated by pt_node_to_partition_info. Hash partitions create
hashsize children; range and list partitions create one child
per PT_PARTS element.
Hash partitions appear in the schema as
base_class__p__p0, base_class__p__p1, …, all sub-classes of
the base. Each child has its own heap and B-trees; the parent’s
heap is empty. Routing of DML to the correct child happens later,
in pt_resolve_partition_spec (parser) and
btree_find_root_with_key (executor).
Cross-reference: see the dedicated cubrid-partition analysis for the routing-side details.
Trigger DDL — three short handlers
Section titled “Trigger DDL — three short handlers”do_create_trigger, do_drop_trigger, do_alter_trigger,
do_rename_trigger and do_remove_trigger are all in
execute_statement.c. They are thin wrappers around
tr_create_trigger, tr_drop_trigger, etc., from
src/object/trigger_manager.c.
// do_create_trigger — execute_statement.cclass_ = db_find_class (PT_TR_TARGET_CLASS (target));/* ... extract event/condition/action, time, priority, comment ... */trigger = tr_create_trigger (name, status, priority, event, class_, attribute, cond_time, cond_source, action_time, action_type, action_source, comment);
if (smclass != NULL && smclass->users != NULL && TM_TRAN_ISOLATION () < TRAN_REP_READ) error = locator_all_flush ();The flush of the entire workspace is a CUBRID quirk: trigger
metadata travels through the same heap row as the class
(SM_CLASS::triggers), so installing a trigger on a class whose
sub-classes are temporary objects requires a full flush to make
the trigger visible across the hierarchy. The repeatable-read
guard short-circuits the flush when isolation already provides
consistency.
Cross-reference: the cubrid-trigger analysis in this folder
covers tr_* internals.
Locator integration — DDL routes through the same MOP path as DML
Section titled “Locator integration — DDL routes through the same MOP path as DML”DDL never bypasses the locator. Every catalog mutation reaches the server through one of:
locator_reserve_class_name— server-side hash insert into the classname-to-OID table; returns a temporary OID.locator_add_class— workspace cache install with the temporary OID; setsSCH_M_LOCKon the class MOP.locator_flush_class— pushes the class instance to the server, wherexlocator_forcetranslates it intocatcls_insert_catalog_classes/catcls_update_catalog_classes.locator_remove_class— decache instances, destroy the heap, delete the classname.locator_create_heap_if_needed— ensureHFIDis set; callsheap_createon the server.locator_all_flush— drain every dirty MOP before replication records the DDL.
The crucial property is that none of these are special DDL
opcodes. A class is just an instance of _db_class; flushing it
to the server is the same operation as flushing any other dirty
MOP. The catalog write happens because the destination heap of
the class MOP is the catalog heap.
// locator_add_class — locator_cl.c (condensed)class_mop = ws_find_class (classname); /* deleted-and-resurrected case is also handled */locator_get_reserved_class_name_oid (classname, &class_temp_oid);
/* Convert root-class lock to IX_LOCK so the new class can be IX-locked under it */lock = ws_get_lock (sm_Root_class_mop);if (lock != NULL_LOCK) ws_set_lock (sm_Root_class_mop, lock_conv (lock, IX_LOCK));else locator_lock (sm_Root_class_mop, LC_CLASS, IX_LOCK, LC_FETCH_CURRENT_VERSION);
class_mop = ws_cache_with_oid (class_obj, &class_temp_oid, sm_Root_class_mop);if (class_mop != NULL) { ws_dirty (class_mop); ws_set_lock (class_mop, SCH_M_LOCK); }return class_mop;Two design points are worth highlighting:
- The class MOP is installed under the root class MOP
(
sm_Root_class_mop), which holds anIX_LOCKfor the duration of the DDL — this is how concurrent DDLs serialise against each other through the locator without needing a global schema lock. - The new class is cached under its temporary OID until
flushed, at which point the server assigns a permanent OID and
ws_update_oidrewrites the workspace handle.
Cross-reference: the cubrid-locator analysis covers the
classname hash table, xlocator_assign_oid_batch, and the
temporary-to-permanent-OID rewrite in detail.
Catalog write — catcls_insert_catalog_classes
Section titled “Catalog write — catcls_insert_catalog_classes”The server side of a CREATE TABLE flush lands in
catcls_insert_catalog_classes (src/storage/catalog_class.c).
// catcls_insert_catalog_classes — catalog_class.c (condensed)value_p = catcls_get_or_value_from_class_record (thread_p, record_p);class_oid_p = &ct_Class.cc_classoid; /* OID of _db_class */cls_info_p = catalog_get_class_info (thread_p, class_oid_p, NULL);hfid_p = &cls_info_p->ci_hfid;heap_scancache_start_modify (thread_p, &scan, hfid_p, class_oid_p, SINGLE_ROW_UPDATE, NULL);catcls_insert_instance (thread_p, value_p, &oid, &root_oid, class_oid_p, hfid_p, &scan);heap_scancache_end_modify (thread_p, &scan);The route is: deserialise the class record into an OR_VALUE
tree, find the catalog _db_class by its well-known OID
(ct_Class.cc_classoid), open a heap scancache in modify mode,
and call catcls_insert_instance which performs the actual
heap insert plus B-tree updates for every catalog index. The
write is a single MVCC transaction — the same MVCC machinery
that protects user tables protects the catalog.
catcls_update_catalog_classes is the same, but for ALTER. Note
the convenient fall-through: if the class name is not found, the
update is upgraded to an insert.
// catcls_update_catalog_classes — catalog_class.c (condensed)catcls_find_oid_by_class_name (thread_p, name_p, &oid);if (OID_ISNULL (&oid)) /* upgrade UPDATE to INSERT */ return catcls_insert_catalog_classes (thread_p, record_p);value_p = catcls_get_or_value_from_class_record (thread_p, record_p);/* open scancache; heap_update_logical inside catcls_update_instance */catcls_delete_catalog_classes is symmetric: find the OID by
name, open the heap scancache for SINGLE_ROW_DELETE, call
catcls_delete_instance, then catcls_remove_entry to evict
from the in-memory class cache. In MVCC, the row is not
physically removed — the comment in the source is explicit:
/* in MVCC, do not physically remove the row */ — instead it
is logically deleted and reclaimed by VACUUM.
Cache invalidation — schema version, class_object, XASL
Section titled “Cache invalidation — schema version, class_object, XASL”Three caches need to know when a class changes:
flowchart LR Bump[sm_bump_local_schema_version<br/>schema_manager.c] Bump --> Inst[install_new_representation<br/>classobj_install_template] Inst --> WS[Workspace MOP cache<br/>SM_CLASS swap] Bump --> XASL[XASL cache miss on next compile<br/>see cubrid-xasl-cache] Inst --> Repr[representation chain<br/>old reps preserved for legacy heap rows] WS --> Reset[sm_reset_descriptors<br/>per-MOP attribute cache]
- Workspace class-object cache. The MOP itself owns the
SM_CLASSpointer.classobj_install_templatewalks every field and copies from the flattened template into the live class. After this point, everyau_fetch_class (mop, ..., AU_FETCH_READ, ...)returns the new shape. There is no per-statement cache invalidation message — the swap is the invalidation, and any executor that holds a stale pointer would have lost its lock at the same time. - Attribute and method descriptor caches.
sm_reset_descriptorsclears the per-class attribute descriptor cache (used bydb_get_attribute_descriptorand the fast-path attribute getters). Old descriptor handles will fail on next use rather than dereference freed memory. - XASL cache. CUBRID’s plan cache keys each entry on a
(SHA1(SQL), schema_version)pair.sm_bump_local_schema_versionincrements the local counter and the next plan compile that references the changed class sees a miss. The XASL cache is not purged eagerly; it ages out naturally. - Trigger cache.
invalidate_unused_triggersis called frominstall_new_representationto markTR_TRIGGERentries associated with attributes that no longer exist as deleted. The authoritative trigger list is rebuilt from_db_triggeron next access. - Representation chain.
build_storage_orderdecides whether the change forces anew representationof the class. The old representations are preserved in the catalog so that historical heap rows whose schema-version stamp predates the change can still be read; the executor consults the right representation viaor_class_rep_id. This is CUBRID’s analogue of Postgres’attnumplus drop-column-marker scheme.
Cross-references: cubrid-class-object covers the SM_CLASS /
SM_TEMPLATE data structures; cubrid-xasl-cache covers the
plan cache invalidation policy; cubrid-catalog-manager covers
the disk catalog layout that catcls_* writes into.
Transactional semantics — savepoints everywhere
Section titled “Transactional semantics — savepoints everywhere”CUBRID’s DDL is fully transactional, and the discipline that makes it so is system savepoints. Every DDL handler establishes a unique savepoint at entry and rolls back to it on any errpath:
// savepoint identifiers — execute_schema.c (selected; full list lives at the top of the file)#define UNIQUE_SAVEPOINT_CREATE_ENTITY "cREATEeNTITY"#define UNIQUE_SAVEPOINT_DROP_ENTITY "dROPeNTITY"#define UNIQUE_SAVEPOINT_MULTIPLE_ALTER "mULTIPLEaLTER"#define UNIQUE_SAVEPOINT_ADD_ATTR_MTHD "aDDaTTRmTHD"#define UNIQUE_SAVEPOINT_CHANGE_ATTR "cHANGEaTTR"#define UNIQUE_SAVEPOINT_RENAME "rENAME"#define UNIQUE_SAVEPOINT_TRUNCATE "tRUnCATE"#define UNIQUE_SAVEPOINT_REPLACE_VIEW "rEPlACE"#define UNIQUE_SAVEPOINT_ALTER_INDEX "aLTERiNDEX"/* plus the user/grant/revoke variants and partition savepoints */The strange casing ("cREATEeNTITY") is intentional: it makes
collisions with user-named savepoints astronomically unlikely.
The savepoint is taken with tran_system_savepoint, which writes
a CLR (compensation log record) into the WAL — on rollback,
recovery walks the WAL backwards from the abort record to the
savepoint LSN and undoes everything in between.
A CREATE TABLE … AS SELECT that fails halfway through the
INSERT is rolled back to cREATEeNTITY; the catalog row, the
heap, every B-tree, and any partition children are all undone.
A ROLLBACK after a successful CREATE TABLE undoes the create
just as cleanly. This is the contract that lets CUBRID claim
atomic DDL in the MySQL 8.0 sense.
The replication tail records the schema change as a LOG_REPLICATION_DDL
entry by calling do_replicate_statement; the supplemental log
captures the affected class OIDs and the parsed statement text for
CDC. Both are deferred until after the local DDL has succeeded
and locator_all_flush () has reified every dirty MOP.
Source Walkthrough
Section titled “Source Walkthrough”Top-level dispatch — execute_statement.c
Section titled “Top-level dispatch — execute_statement.c”do_statement (parser, statement)— single-statement entry point; the giantswitch (statement->node_type)is the DDL router.do_execute_statement— re-prepared variant; walks the same switch but dispatches via the prepared XASL when applicable.do_check_internal_statements (parser, stmt, do_func)— currently a pass-through todo_func; the legacy text-domain internal-statements hook is#if 0-guarded.do_reserve_classinfo,do_reserve_oidinfo— record the affected class OID before DROP/DROP_SERIAL so the supplemental log can name it after the catalog row is gone.do_supplemental_statement— emits the supplemental log record for CDC.do_replicate_statement— emits the schema-replication record for HA.do_create_serial,do_alter_serial,do_drop_serial— serial DDL; calls into_db_serial.do_create_trigger,do_drop_trigger,do_alter_trigger,do_rename_trigger,do_remove_trigger,do_set_trigger,do_get_trigger,do_execute_trigger— trigger DDL; delegate totr_*intrigger_manager.c.
CREATE TABLE / DROP / RENAME — execute_schema.c
Section titled “CREATE TABLE / DROP / RENAME — execute_schema.c”do_create_entity—CREATE TABLE/CREATE VIEWdriver.do_create_local— column/constraint/method/super-class population of the template.execute_create_select_query— theCREATE TABLE … AS SELECTdesugaring: synthesises aPT_INSERTand re-entersdo_statement.create_select_to_insert_into— synthesises thePT_INSERTparse tree.do_drop—DROP TABLEdriver.drop_class_name— name → MOP →db_drop_class_ex.truncate_class_name— name → MOP →db_truncate_class.do_truncate—TRUNCATEdriver.do_rename—RENAMEdriver; usesacquire_locks_for_multiple_renameto atomically lock and reserve names for chained renames.do_rename_internal— performs the actualsm_rename_class.update_locksets_for_multiple_rename,acquire_locks_for_multiple_rename— the lock-and-reserve dance forRENAME a TO b, b TO c, c TO a.do_recreate_renamed_class_indexes,do_copy_indexes— index recreation after RENAME andCREATE LIKE.
ALTER TABLE — execute_schema.c
Section titled “ALTER TABLE — execute_schema.c”do_alter,do_alter_one_clause_with_template— multi-clause driver and the single-clause template path used by ADD/DROP column, query ops, and partition ops.do_alter_clause_rename_entity,do_alter_clause_add_index,do_alter_clause_drop_index,do_alter_change_auto_increment,do_alter_clause_change_attribute,do_alter_change_owner,do_alter_change_default_cs_coll,do_alter_change_tbl_comment,do_alter_change_col_comment— one perPT_ALTER_CODEthat bypasses the catch-all template path.do_change_att_schema_only— schema-only column redefinition.do_run_update_query_for_new_notnull_fields,do_run_update_query_for_new_default_expression_fields,do_update_new_notnull_cols_without_default,do_update_new_cols_with_default_expression,do_run_upgrade_instances_domain— instance-level fixups for ALTER ADD COLUMN with NOT NULL / DEFAULT / domain widening.do_drop_att_constraints,do_recreate_att_constraints,do_save_all_indexes,do_drop_saved_indexes,do_recreate_saved_indexes— constraint and index preservation around CHANGE COLUMN.do_alter_index_status— ALTER INDEX … VISIBLE/INVISIBLE.
Index DDL — execute_schema.c
Section titled “Index DDL — execute_schema.c”do_create_index— PT_CREATE_INDEX driver.do_drop_index— PT_DROP_INDEX driver.do_alter_index— dispatch to rebuild/rename/comment/status.do_alter_index_rebuild,do_alter_index_rename,do_alter_index_comment.create_or_drop_index_helper— the shared body.get_reverse_unique_index_type,get_index_type_qualifiers— PT-flag → DB_CONSTRAINT_TYPE adapters.
Partition DDL — execute_schema.c
Section titled “Partition DDL — execute_schema.c”do_create_partitionsynthesises aPT_CREATE_ENTITYper child and recurses intodo_create_local.do_alter_partitioning_pre/do_alter_partitioning_post— two-phase ALTER … PARTITION dispatcher; per-op pairs aredo_remove_partition_pre/_post,do_coalesce_partition_pre/_post,do_reorganize_partition_pre/_post, plusdo_promote_partition_list,do_promote_partition_by_name,do_promote_partition,do_analyze_partition.- Partition-aware helpers:
do_check_partitioned_class,do_get_partition_parent,do_is_partitioned_subclass,do_drop_partitioned_class,do_rename_partition,do_get_partition_size,do_get_partition_keycol,do_drop_partition_list,do_create_partition_constraints,do_create_partition_constraint,do_redistribute_partitions_data.
User and authorisation DDL — execute_schema.c
Section titled “User and authorisation DDL — execute_schema.c”do_grant,do_revoke— privilege management.do_create_user,do_drop_user,do_alter_user— user DDL (member sets, password, comment).
Trigger and serial DDL — execute_statement.c
Section titled “Trigger and serial DDL — execute_statement.c”do_create_trigger— wrapstr_create_trigger.do_drop_trigger— wrapstr_drop_triggerper spec list.do_alter_trigger— wrapstr_set_priority/tr_set_status.do_rename_trigger— wrapstr_rename_trigger.do_create_serial,do_alter_serial,do_drop_serial— CRUD on the_db_serialsystem class, plusdo_get_serial_obj_idlookup helper.
Template machinery — db_temp.c, schema_template.c, schema_manager.c
Section titled “Template machinery — db_temp.c, schema_template.c, schema_manager.c”- Public
dbt_*API:dbt_create_class,dbt_create_vclass,dbt_edit_class,dbt_copy_class,dbt_finish_class,dbt_abort_class, plus the per-componentdbt_add_attribute/dbt_add_constraint/dbt_drop_*/dbt_*_query_specmutators. - Internal factory:
smt_def_class,smt_def_typed_class,smt_edit_class_mop,smt_copy_class_mop,smt_copy_class,smt_quit,def_class_internal. SM_TEMPLATElifecycle (class_object.c):classobj_make_template,classobj_make_template_like,classobj_install_template,classobj_free_template.- Inheritance merge and physical layout:
flatten_template,flatten_subclasses,build_storage_order,assign_attribute_id,assign_method_id. - Schema-graph primitives:
update_class,update_supers,update_supers_drop,update_subclasses,lock_supers,lock_supers_drop,lock_subclasses,lockhint_subclasses. - Physical mutation:
install_new_representation,transfer_disk_structures,allocate_disk_structures,deallocate_index,rem_class_from_index. - Public class-update endpoints:
sm_finish_class,sm_update_class,sm_update_class_auto,sm_update_class_with_auth. Drop machinery:sm_delete_class_mop,remove_class_triggers,sm_drop_cascade_foreign_key,do_drop_partitioned_class. - DDL fence counter:
sm_bump_local_schema_version,sm_local_schema_version.
Locator and catalog — locator_cl.c, locator_sr.c, catalog_class.c
Section titled “Locator and catalog — locator_cl.c, locator_sr.c, catalog_class.c”- Classname hash:
locator_reserve_class_name,locator_delete_class_name,locator_get_reserved_class_name_oid,locator_force_drop_class_name_entry. - Client-side class MOP lifecycle:
locator_add_class,locator_remove_class,locator_update_class,locator_flush_class,locator_create_heap_if_needed,locator_prepare_rename_class. Workspace drains:locator_all_flush,locator_flush_all_instances. - Server-side:
xlocator_forcedispatches the flush and routes catalog-class instances tocatcls_*;xlocator_remove_class_from_indexandxlocator_assign_oid_batchhandle multi-class index removal and temporary→permanent OID rewriting. - Catalog write API:
catcls_insert_catalog_classes,catcls_update_catalog_classes,catcls_delete_catalog_classes,catcls_update_class_stats,catcls_remove_entry,catcls_compile_catalog_classes. OR_VALUE↔ heap-row marshalling:catcls_get_or_value_from_class,catcls_get_or_value_from_class_record,catcls_put_or_value_into_record,catcls_insert_instance,catcls_delete_instance,catcls_update_instance. Name resolution at ALTER/DROP:catcls_find_oid_by_class_name.
Position hints as of this revision
Section titled “Position hints as of this revision”| Symbol | File | Line |
|---|---|---|
do_statement (DDL switch) | src/query/execute_statement.c | 3244 |
do_check_internal_statements | src/query/execute_statement.c | 4270 |
do_create_trigger | src/query/execute_statement.c | 6661 |
do_drop_trigger | src/query/execute_statement.c | 6801 |
do_alter_trigger | src/query/execute_statement.c | 6860 |
do_create_serial | src/query/execute_statement.c | 1406 |
do_replicate_statement | src/query/execute_statement.c | 16136 |
do_supplemental_statement | src/query/execute_statement.c | 15515 |
do_alter | src/query/execute_schema.c | 1770 |
do_alter_one_clause_with_template | src/query/execute_schema.c | 409 |
do_alter_clause_rename_entity | src/query/execute_schema.c | 1558 |
do_alter_clause_change_attribute | src/query/execute_schema.c | 9942 |
drop_class_name | src/query/execute_schema.c | 2582 |
do_drop | src/query/execute_schema.c | 2607 |
acquire_locks_for_multiple_rename | src/query/execute_schema.c | 2737 |
do_rename | src/query/execute_schema.c | 2890 |
create_or_drop_index_helper | src/query/execute_schema.c | 3027 |
do_create_index | src/query/execute_schema.c | 3330 |
do_drop_index | src/query/execute_schema.c | 3370 |
do_alter_index | src/query/execute_schema.c | 3995 |
do_create_partition | src/query/execute_schema.c | 4037 |
do_alter_partitioning_pre | src/query/execute_schema.c | 6017 |
do_alter_partitioning_post | src/query/execute_schema.c | 6165 |
do_add_attributes | src/query/execute_schema.c | 7735 |
do_add_constraints | src/query/execute_schema.c | 7950 |
do_check_fk_constraints | src/query/execute_schema.c | 8303 |
do_create_local | src/query/execute_schema.c | 8766 |
execute_create_select_query | src/query/execute_schema.c | 8946 |
do_create_entity | src/query/execute_schema.c | 9025 |
truncate_class_name | src/query/execute_schema.c | 9855 |
do_truncate | src/query/execute_schema.c | 9880 |
do_grant | src/query/execute_schema.c | 1888 |
do_create_user | src/query/execute_schema.c | 2114 |
dbt_create_class | src/compat/db_temp.c | 76 |
dbt_edit_class | src/compat/db_temp.c | 132 |
dbt_finish_class | src/compat/db_temp.c | 225 |
smt_def_class | src/object/schema_template.c | 735 |
smt_edit_class_mop | src/object/schema_template.c | 753 |
update_class | src/object/schema_manager.c | 13148 |
sm_finish_class | src/object/schema_manager.c | 13431 |
sm_delete_class_mop | src/object/schema_manager.c | 13584 |
install_new_representation | src/object/schema_manager.c | 12366 |
transfer_disk_structures | src/object/schema_manager.c | 11924 |
sm_bump_local_schema_version | src/object/schema_manager.c | 6717 |
locator_add_class | src/transaction/locator_cl.c | 5378 |
locator_create_heap_if_needed | src/transaction/locator_cl.c | 5472 |
locator_remove_class | src/transaction/locator_cl.c | 5652 |
locator_flush_class | src/transaction/locator_cl.c | 4890 |
xlocator_force | src/transaction/locator_sr.c | 7129 |
catcls_insert_catalog_classes | src/storage/catalog_class.c | 4310 |
catcls_update_catalog_classes | src/storage/catalog_class.c | 4573 |
catcls_delete_catalog_classes | src/storage/catalog_class.c | 4379 |
catcls_get_or_value_from_class_record | src/storage/catalog_class.c | 3552 |
Cross-check Notes
Section titled “Cross-check Notes”- cubrid-class-object describes
SM_CLASSandSM_TEMPLATEas data structures; this doc describes the workflow by which a template becomes a class.classobj_install_templateis the structural contract; the representation chain (new_,old_,representations) is populated bybuild_storage_orderhere and consumed byor_class_rep_idthere. - cubrid-catalog-manager covers the on-disk layout of
_db_class/_db_attribute/_db_index. This doc covers the call paths intocatcls_insert_catalog_classes/catcls_update_catalog_classes/catcls_delete_catalog_classes; the marshalling is one-to-one withOR_VALUEtrees described there. - cubrid-locator owns the classname hash table, lock
conversion under
sm_Root_class_mop, and the temporary→permanent OID rewrite at flush time; this doc names the call sites (locator_add_class,locator_remove_class,locator_create_heap_if_needed,locator_flush_class,xlocator_force). - cubrid-trigger owns
tr_create_trigger/tr_drop_triggerand the_db_triggercatalog row. The cross-cutting concerns areinvalidate_unused_triggersinsideinstall_new_representationandremove_class_triggersinsidesm_delete_class_mop. - cubrid-btree and cubrid-partition: index DDL delegates
to
sm_add_constraint/sm_drop_constraintand ultimately tobtree_create_index/btree_delete_index; partition DDL fans into per-child class creation, with routing in the partition doc. - cubrid-xasl-cache covers how the bumped
sm_local_schema_versioninteracts with the plan-cache key and prepared-statement state. - Drift watch. Line numbers in the position-hint table are
stable as of late-2025 worktree state; edits to
execute_statement.cfor newPT_NODE_TYPEcodes tend to push the DDL switch downward. Anchor on symbol names — they are stable across releases since 11.0.
Open Questions
Section titled “Open Questions”- Online schema change.
ALTER TABLEtakesSCH_M_LOCKfor its full duration; a row-log / ghost-copy à la InnoDB online DDL would let concurrent DML proceed during long ALTERs (ADD COLUMN with non-trivial DEFAULT, domain widening). The natural integration points aredo_run_update_query_for_new_notnull_fieldsanddo_run_upgrade_instances_domain. - Concurrent DDL on disjoint classes. Two
CREATE TABLEstatements on different classes serialise through theIX_LOCKonsm_Root_class_mopplus the classname-hash insert. Whether a finer-grained schema lock would help in practice depends on contention measurements not in the source. - Per-partition online add column.
do_add_attributeruns once on the parent template and propagates throughflatten_subclasses. The representation chain carries a per-class representation ID, so per-partition parallel migration is conceivable but not implemented. - Atomicity of trigger DDL across HA.
locator_all_flush ()insidedo_create_triggercovers local consistency, but the schema-replication record is written by the outerdo_replicate_statement. Behaviour under slave-side crash recovery for hierarchical triggers is not exhaustively documented. do_check_internal_statementsreactivation. The wrapper hosts a substantial#if 0block for TEXT-domain auxiliary statements. If TEXT returns, the savepoint discipline must be reconciled with the per-DDL savepoints already taken by the dispatched handlers.- MVCC delete of catalog rows.
catcls_delete_catalog_classesdoes not physically remove the row; whether VACUUM on the catalog heap is treated specially (e.g. forced after every DROP) is not obvious fromexecute_schema.calone.
Sources
Section titled “Sources”Synthesised from the CUBRID source tree at the revision the position-hint table records.
Code paths consumed:
src/query/execute_schema.c—do_create_entity,do_alter,do_drop,do_truncate,do_rename,do_create_index/do_drop_index/do_alter_index*,do_create_partition,do_alter_partitioning_pre/_post,do_grant/do_revoke,do_create_user/do_drop_user/do_alter_user, plus thedo_add_*template-mutation helpers.src/query/execute_statement.c—do_statementswitch, replication and supplemental-log tail, trigger / serial / stored-procedure DDL handlers.src/object/schema_manager.c—update_class,sm_finish_class,sm_delete_class_mop,install_new_representation,transfer_disk_structures,sm_bump_local_schema_version.src/object/schema_template.c—smt_def_class,smt_edit_class_mop,smt_copy_class_mop,smt_quit.src/object/class_object.c—classobj_make_template,classobj_install_template,classobj_make_class,classobj_free_template.src/object/object_template.c—obt_quit,obt_assign,obt_apply_assignments,obt_update.src/compat/db_temp.c— publicdbt_*template API.src/transaction/locator_cl.c—locator_reserve_class_name,locator_add_class,locator_create_heap_if_needed,locator_remove_class,locator_flush_class,locator_update_class,locator_all_flush.src/transaction/locator_sr.c—xlocator_force,xlocator_remove_class_from_index,xlocator_assign_oid_batch,locator_force_drop_class_name_entry.src/storage/catalog_class.c—catcls_insert_catalog_classes,catcls_update_catalog_classes,catcls_delete_catalog_classes,catcls_get_or_value_from_class_record,catcls_compile_catalog_classes.src/parser/csql_grammar.y— DDL grammar that producesPT_CREATE_ENTITY,PT_ALTER,PT_DROP,PT_CREATE_INDEX,PT_ALTER_INDEX,PT_RENAME,PT_TRUNCATE,PT_CREATE_TRIGGER,PT_CREATE_SERIAL,PT_CREATE_USER.
Theoretical references:
- Silberschatz et al., Database System Concepts, 7th ed., §5.2 (DDL syntax), §15.5 (System Catalog), §17 (Recovery and the atomicity of DDL).
- Petrov, Database Internals, Ch. 4 (Schema management as a special case of B-tree updates).
- Postgres source:
src/backend/commands/tablecmds.c(DefineRelation,ATExecAddColumn),src/backend/utils/cache/relcache.c(relcache invalidation),src/backend/utils/cache/inval.c(CommandCounterIncrement). - MySQL 8.0 Atomic DDL design notes (
mysql.tables,dd::cache, the DDL log). - Oracle Concepts Guide (Library Cache Locks and Pins; DDL implicit commit).