Skip to content

Conversation

@liuys-dase
Copy link
Contributor

@liuys-dase liuys-dase commented Dec 19, 2025

feat(storage): support catalog transaction and add global transaction structure

Type

  • feat: (new feature)
  • fix: (bug fix)
  • docs: (doc update)
  • refactor: (refactor code)
  • test: (test code)
  • chore: (other updates)

Scope

  • query: (query engine)
    • parser: (frontend parser)
    • planner: (frontend planner)
    • optimizer: (query optimizer)
    • executor: (execution engine)
    • op: (operators)
  • storage: (storage engine)
    • mvcc: (multi version concurrency control)
    • schema: (graph model and topology)
  • tool: (tools)
    • cli: (cli)
    • sdk: (sdk)
  • none: (N/A)

Description

Issue: #

Checklist

  • I have prepared the pull request title according to the requirements.
  • I have successfully run all unit tests and integration tests.
  • I have already rebased the latest master branch.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have made corresponding changes to the documentation.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces catalog transaction support and a unified global transaction structure that coordinates updates across both catalog and graph storage layers. The implementation follows a two-phase commit protocol where catalog changes are validated first, graph changes are committed, and then catalog changes are published using the same commit timestamp.

Key changes include:

  • Introduction of Transaction struct that coordinates catalog and graph sub-transactions with shared timestamps and isolation levels
  • New versioned catalog implementation with MVCC support for schema operations
  • Refactoring of timestamp and isolation level types from minigu-transaction to minigu-common for shared access
  • Removal of trait-based abstractions (Transaction, GraphTxnManager) in favor of concrete types

Reviewed changes

Copilot reviewed 42 out of 43 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
minigu/transaction/src/transaction.rs Implements global Transaction with two-phase commit coordination between catalog and graph
minigu/transaction/src/manager.rs Provides TransactionManager for creating unified transactions
minigu/catalog/src/txn/* New catalog transaction infrastructure with versioned maps and MVCC
minigu/catalog/src/memory/* Updates catalog implementations to support transactional operations
minigu/common/src/transaction.rs Moves IsolationLevel enum to common for shared access
minigu/common/src/timestamp.rs Moves timestamp types and error handling to common
minigu/storage/src/tp/transaction.rs Removes Transaction trait, adds GraphTxnView for polymorphic access
minigu/storage/src/tp/memory_graph.rs Updates graph operations to accept GraphTxnView instead of concrete types
minigu/context/src/graph.rs Integrates TransactionManager into GraphContainer for unified transaction creation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ColinLeeo ColinLeeo self-requested a review December 23, 2025 09:18
Comment on lines 82 to +351
label_set: LabelSet,
edge_type: Arc<MemoryEdgeTypeCatalog>,
) -> bool {
match self.edge_type_map.entry(label_set) {
Entry::Occupied(_) => false,
Entry::Vacant(e) => {
e.insert(edge_type);
true
let txn = match txn_manager().begin_transaction(IsolationLevel::Serializable) {
Ok(txn) => txn,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized that all of our metadata operations are wrapped inside an anonymous transaction. In that case, if a user starts a transaction block in the CLI, how should this part of the transaction be handled? Will metadata transactional operations conflict with data transactional operations?

Comment on lines 41 to 44
) -> CatalogResult<Option<DirectoryOrSchema>> {
let _ = txn;
self.get_child(name)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this get_child_txn will ignore child's txn?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to explicitly provide functions with the txn suffix?

Comment on lines +61 to +67
) -> Result<Arc<CatalogTxn>, CatalogTxnError> {
let txn_id = if let Some(id) = txn_id {
global_transaction_id_generator().update_if_greater(id)?;
id
} else {
global_transaction_id_generator().next()?
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why should call global_transaction_id_generator().update_if_greater(id)?; here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When starting a transaction with a specific ID (e.g., during WAL replay), we must advance the global generator. This ensures that subsequent calls to next() (for new transactions) will allocate strictly higher IDs, preventing ID collisions.

Comment on lines +39 to +45
pub fn begin_transaction(
&self,
isolation_level: IsolationLevel,
) -> Result<Transaction, TxnError> {
self.txn_mgr.begin_transaction(isolation_level)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering: many metadata operations use a pattern where a transaction is automatically started and then an attempt is made to commit it. In that case, what is the purpose of maintaining a separate transaction related to data inside graphContainer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to the reply in the first comment.

Comment on lines +108 to +112
let txn = txn_manager()
.begin_transaction(IsolationLevel::Serializable)
.map_err(|e| CatalogError::External(Box::new(e)))?;
let res = self.get_child_txn(name, txn.as_ref());
txn.abort().ok();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use snapshot will be better?

@ColinLeeo
Copy link
Contributor

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants