Branch Management Guide

Overview

MatrixOne’s branch management feature allows you to create isolated branches of your database for development, testing, and experimentation without affecting the main database. Branches are lightweight and efficient, making them ideal for:

  • Development and testing

  • Feature branches for different teams

  • Safe experimentation with schema changes

  • Parallel development workflows

Version Requirement: MatrixOne 3.0.5 or higher

Creating and Managing Branches

Basic Branch Operations

from matrixone import Client

client = Client()
client.connect(database='test')

# Create a table branch
client.branch.create_table_branch(
    target_table='users_branch',
    source_table='users'
)

# Create a database branch
client.branch.create_database_branch(
    target_database='dev_db',
    source_database='main_db'
)

# Delete a table branch
client.branch.delete_table_branch('users_branch')

# Delete a database branch
client.branch.delete_database_branch('dev_db')

Simplified API

For convenience, you can use the simplified API that auto-detects table vs database operations:

from matrixone import Client

client = Client()
client.connect(database='test')

# Create table branch (simplified)
client.branch.create('users_branch', 'users')

# Create database branch (simplified)
client.branch.create('dev_db', 'main_db', database=True)

# Delete table branch (simplified)
client.branch.delete('users_branch')

# Delete database branch (simplified)
client.branch.delete('dev_db', database=True)

Branch Workflow Example

from matrixone import Client

client = Client()
client.connect(database='production')

# Create a development branch from production database
client.branch.create_database_branch(
    target_database='feature_branch',
    source_database='production'
)

# Make changes in the branch database
client.execute("USE feature_branch")
client.execute("""
    ALTER TABLE users ADD COLUMN new_field VARCHAR(100)
""")

# Test the changes
result = client.execute("SELECT * FROM users LIMIT 1")
print(result.fetchall())

# Compare with original
diffs = client.branch.diff_table(
    table='feature_branch.users',
    against_table='production.users'
)
print(f"Differences: {diffs}")

# If satisfied, merge back to main
client.branch.merge_table(
    source_table='feature_branch.users',
    target_table='production.users',
    conflict_strategy='accept'
)

# Clean up
client.branch.delete_database_branch('feature_branch')

Use Cases

Development and Testing

Create isolated branches for each feature or bug fix:

from matrixone import Client

client = Client()
client.connect(database='main')

# Create a branch for a new feature
client.branch.create_table_branch(
    target_table='feature_users',
    source_table='users'
)

# Work on the feature in isolation
client.execute("USE main")
client.execute("""
    INSERT INTO feature_users (name, email) VALUES ('test', 'test@example.com')
""")

# Run tests and development
result = client.execute("SELECT * FROM feature_users")
print(result.fetchall())

Schema Experimentation

Test schema changes safely without affecting production:

from matrixone import Client

client = Client()
client.connect(database='production')

# Create a test branch
client.branch.create_database_branch(
    target_database='schema_test',
    source_database='production'
)

# Switch to test database and experiment with schema changes
client.execute("USE schema_test")
client.execute("ALTER TABLE products ADD COLUMN category_id INT")
client.execute("CREATE INDEX idx_category ON products(category_id)")

# Verify changes work correctly
result = client.execute("SELECT * FROM products LIMIT 1")
print(result.fetchall())

# If successful, apply to production
# If not, simply delete the branch
client.branch.delete_database_branch('schema_test')

Parallel Development

Multiple teams can work on different branches simultaneously:

from matrixone import Client

client = Client()
client.connect(database='main')

# Team A creates their branch
client.branch.create_database_branch(
    target_database='team_a_feature',
    source_database='main'
)

# Team B creates their branch
client.branch.create_database_branch(
    target_database='team_b_feature',
    source_database='main'
)

# Both teams work independently
# Changes in one branch don't affect the other
client.execute("USE team_a_feature")
client.execute("INSERT INTO users (name) VALUES ('Team A User')")

client.execute("USE team_b_feature")
client.execute("INSERT INTO users (name) VALUES ('Team B User')")

Comparing and Merging Branches

Compare differences between branches and merge them:

from matrixone import Client, DiffOutput, MergeConflictStrategy

client = Client()
client.connect(database='test')

# Create a branch
client.branch.create_table_branch(
    target_table='users_branch',
    source_table='users'
)

# Make changes in the branch
client.execute("INSERT INTO users_branch (name) VALUES ('New User')")

# Get count of differences (performance optimized)
diff_count = client.branch.diff_table(
    table='users_branch',
    against_table='users',
    output=DiffOutput.COUNT
)
count_value = diff_count[0][0] if diff_count else 0
print(f"Differences found: {count_value}")

# Get detailed differences
diffs = client.branch.diff_table(
    table='users_branch',
    against_table='users',
    output=DiffOutput.ROWS  # or just omit, it's the default
)
print(f"Detailed differences: {len(diffs)}")

# Merge branch back to original (skip conflicts)
client.branch.merge_table(
    source_table='users_branch',
    target_table='users',
    on_conflict=MergeConflictStrategy.SKIP
)

# Or accept source values on conflict
client.branch.merge_table(
    source_table='users_branch',
    target_table='users',
    on_conflict=MergeConflictStrategy.ACCEPT
)

# Clean up
client.branch.delete_table_branch('users_branch')

Point-in-Time Branching

Create branches from specific snapshots:

from matrixone import Client

client = Client()
client.connect(database='production')

# Create a snapshot first
client.snapshot.create_snapshot(
    snapshot_name='daily_backup_2024_01_01',
    database='production'
)

# Create a branch from the snapshot
client.branch.create_table_branch(
    target_table='users_historical',
    source_table='users',
    snapshot_name='daily_backup_2024_01_01'
)

# Now you have a table as it was at that point in time
result = client.execute("SELECT * FROM users_historical")
print(result.fetchall())

Best Practices

  1. Use Descriptive Names: Name branches clearly to indicate their purpose

    # Good
    client.branch.create_database_branch('feature_payment_integration', 'main')
    client.branch.create_database_branch('bugfix_user_login_issue', 'main')
    
    # Avoid
    client.branch.create_database_branch('test', 'main')
    client.branch.create_database_branch('temp', 'main')
    
  2. Clean Up: Delete branches when they’re no longer needed

    # Always clean up after finishing
    client.branch.delete_database_branch('feature_completed_feature')
    
  3. Test Before Merging: Always test changes in a branch before applying to main

    # Create branch
    client.branch.create_table_branch('test_branch', 'main_table')
    
    # Make and test changes
    client.execute("INSERT INTO test_branch VALUES (...)")
    result = client.execute("SELECT * FROM test_branch")
    
    # Verify results before merging
    if result.fetchall():
        client.branch.merge_table('test_branch', 'main_table')
    
  4. Document Changes: Keep track of what changes were made in each branch

  5. Use Consistent Naming: Follow a naming convention for your branches

    • feature/ for new features

    • bugfix/ for bug fixes

    • hotfix/ for urgent fixes

    • experiment/ for experimental work

Performance Considerations

  • Branches are lightweight and don’t duplicate data

  • Creating a branch is fast and efficient

  • Multiple branches can coexist without significant overhead

  • Diff and merge operations are optimized for performance

Limitations

  • Branches are isolated - changes in one branch don’t affect others until merged

  • Merging branches requires explicit operations

  • Some operations may have branch-specific constraints

API Reference

Enums

DiffOutput Enum

from matrixone import DiffOutput

class DiffOutput(str, Enum):
    ROWS = 'rows'    # Return detailed differences (default)
    COUNT = 'count'  # Return only the count of differences

MergeConflictStrategy Enum

from matrixone import MergeConflictStrategy

class MergeConflictStrategy(str, Enum):
    SKIP = 'skip'      # Skip conflicting rows (default)
    ACCEPT = 'accept'  # Accept source values on conflict

Table Branch Operations

# Create table branch
client.branch.create_table_branch(
    target_table: str,
    source_table: str,
    snapshot_name: Optional[str] = None
) -> None

# Delete table branch
client.branch.delete_table_branch(table: str) -> None

# Compare tables
client.branch.diff_table(
    table: str,
    against_table: str,
    snapshot_name: Optional[str] = None,
    output: DiffOutput = DiffOutput.ROWS
) -> List[Dict[str, Any]]

# Merge tables
client.branch.merge_table(
    source_table: str,
    target_table: str,
    on_conflict: Union[str, MergeConflictStrategy] = "skip"
) -> None

Database Branch Operations

# Create database branch
client.branch.create_database_branch(
    target_database: str,
    source_database: str
) -> None

# Delete database branch
client.branch.delete_database_branch(database: str) -> None

Simplified API

# Create branch (auto-detects table vs database)
client.branch.create(
    target: Union[str, Type],
    source: Union[str, Type],
    snapshot: Optional[str] = None,
    database: bool = False
) -> None

# Delete branch (auto-detects table vs database)
client.branch.delete(
    name: Union[str, Type],
    database: bool = False
) -> None

# Compare branches
client.branch.diff(
    table: Union[str, Type],
    against: Union[str, Type],
    snapshot: Optional[str] = None,
    output: DiffOutput = DiffOutput.ROWS
) -> List[Dict[str, Any]]

# Merge branches
client.branch.merge(
    source: Union[str, Type],
    target: Union[str, Type],
    on_conflict: Union[str, MergeConflictStrategy] = "skip"
) -> None

Statement Builders (SQLAlchemy-Style API)

In addition to the BranchManager API above, the SDK provides SQLAlchemy-style statement builders that produce SQL strings independently of the client. These work like select(), insert(), delete(), update() from SQLAlchemy.

from matrixone import (
    create_table_branch,
    create_database_branch,
    delete_table_branch,
    delete_database_branch,
    diff_table_branch,
    merge_table_branch,
)

Creating Branches

# Create table branch from source
stmt = create_table_branch('users_dev').from_table('users')
client.execute(str(stmt))

# With snapshot
stmt = create_table_branch('users_snap').from_table('users', snapshot='snap1')
client.execute(str(stmt))

# Cross-tenant (sys tenant only)
stmt = create_table_branch('users_copy').from_table('users').to_account('tenant1')
client.execute(str(stmt))

# Database branch
stmt = create_database_branch('dev_db').from_database('prod_db')
client.execute(str(stmt))

Deleting Branches

stmt = delete_table_branch('users_dev')
client.execute(str(stmt))

stmt = delete_database_branch('dev_db')
client.execute(str(stmt))

Comparing Branches (Diff)

The diff builder supports all MatrixOne DIFF output options:

# Get all differences
stmt = diff_table_branch('users_dev').against('users')
result = client.execute(str(stmt))

# Count only (performance optimized)
stmt = diff_table_branch('users_dev').against('users').output_count()
result = client.execute(str(stmt))

# Limit rows
stmt = diff_table_branch('users_dev').against('users').output_limit(100)
result = client.execute(str(stmt))

# Export to file
stmt = diff_table_branch('users_dev').against('users').output_file('/tmp/diff.csv')
client.execute(str(stmt))

# With snapshots on both sides
stmt = (
    diff_table_branch('t1').snapshot('snap_a')
    .against('t2', snapshot='snap_b')
    .output_count()
)
client.execute(str(stmt))

Merging Branches

# Merge with skip on conflict (default)
stmt = merge_table_branch('users_dev').into('users')
client.execute(str(stmt))

# Accept source values on conflict
stmt = merge_table_branch('users_dev').into('users').when_conflict('accept')
client.execute(str(stmt))

ORM Model Support

All table builders accept ORM models in place of string table names:

from matrixone.orm import Base, Column, Integer, String

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(100))

stmt = create_table_branch('users_dev').from_table(User)
stmt = diff_table_branch('users_dev').against(User).output_count()
stmt = merge_table_branch('users_dev').into(User).when_conflict('accept')

BranchManager vs Statement Builders

Feature

BranchManager

Statement Builders

Execution

Calls execute internally

Produces SQL string

Client dependency

Requires client

Independent of client

DIFF output options

ROWS, COUNT

COUNT, LIMIT, FILE, AS

Cross-tenant (TO ACCOUNT)

Not supported

Supported

Snapshot on base table

Not supported

Supported

See Also