blob: d6b769a77d45a11dd81200a94b360395e501cba1 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Branch commands for the Paimon CLI.
Adds the top-level ``branch {create,list,delete,rename,fast-forward}``
command. All operations go through the Catalog layer so they get typed
exceptions and work for both filesystem and REST catalogs. Command and
argument names mirror the Java branch procedures (``create_branch`` /
``delete_branch`` / ``rename_branch`` / ``fast_forward``).
"""
import json
import sys
from pypaimon.catalog.catalog_exception import (BranchAlreadyExistException,
BranchNotExistException,
TableNotExistException,
TagNotExistException)
def _open_catalog(args):
"""Load config, build the catalog and validate the ``database.table`` id.
Returns ``(catalog, identifier)``. On any failure, prints to stderr and
exits with a non-zero status (matching the other CLI commands).
"""
from pypaimon.cli.cli import create_catalog, load_catalog_config
identifier = args.table
if len(identifier.split('.')) != 2:
print("Error: Invalid table identifier '{}'. Expected format: "
"'database.table'".format(identifier), file=sys.stderr)
sys.exit(1)
try:
catalog = create_catalog(load_catalog_config(args.config))
except Exception as e:
print("Error: {}".format(e), file=sys.stderr)
sys.exit(1)
return catalog, identifier
def cmd_branch_create(args):
"""Execute ``branch create``."""
catalog, identifier = _open_catalog(args)
try:
catalog.create_branch(identifier, args.branch_name, tag_name=args.tag)
except TableNotExistException:
print("Error: Table '{}' does not exist.".format(identifier),
file=sys.stderr)
sys.exit(1)
except BranchAlreadyExistException:
print("Error: Branch '{}' already exists.".format(args.branch_name),
file=sys.stderr)
sys.exit(1)
except TagNotExistException:
print("Error: Tag '{}' does not exist.".format(args.tag),
file=sys.stderr)
sys.exit(1)
except Exception as e:
print("Error: Failed to create branch: {}".format(e), file=sys.stderr)
sys.exit(1)
if args.tag is not None:
print("Branch '{}' created from tag '{}' on table '{}'.".format(
args.branch_name, args.tag, identifier))
else:
print("Branch '{}' created on table '{}'.".format(
args.branch_name, identifier))
def cmd_branch_delete(args):
"""Execute ``branch delete``."""
catalog, identifier = _open_catalog(args)
try:
catalog.drop_branch(identifier, args.branch_name)
except TableNotExistException:
print("Error: Table '{}' does not exist.".format(identifier),
file=sys.stderr)
sys.exit(1)
except BranchNotExistException:
print("Error: Branch '{}' does not exist.".format(args.branch_name),
file=sys.stderr)
sys.exit(1)
except Exception as e:
print("Error: Failed to delete branch: {}".format(e), file=sys.stderr)
sys.exit(1)
print("Branch '{}' deleted from table '{}'.".format(
args.branch_name, identifier))
def cmd_branch_list(args):
"""Execute ``branch list``."""
catalog, identifier = _open_catalog(args)
try:
branches = catalog.list_branches(identifier)
except TableNotExistException:
print("Error: Table '{}' does not exist.".format(identifier),
file=sys.stderr)
sys.exit(1)
except Exception as e:
print("Error: Failed to list branches: {}".format(e), file=sys.stderr)
sys.exit(1)
if args.format == 'json':
print(json.dumps(branches, ensure_ascii=False))
elif not branches:
print("No branches found.")
else:
for branch in branches:
print(branch)
def cmd_branch_rename(args):
"""Execute ``branch rename``."""
catalog, identifier = _open_catalog(args)
try:
catalog.rename_branch(identifier, args.from_branch, args.to_branch)
except TableNotExistException:
print("Error: Table '{}' does not exist.".format(identifier),
file=sys.stderr)
sys.exit(1)
except BranchNotExistException:
print("Error: Branch '{}' does not exist.".format(args.from_branch),
file=sys.stderr)
sys.exit(1)
except BranchAlreadyExistException:
print("Error: Branch '{}' already exists.".format(args.to_branch),
file=sys.stderr)
sys.exit(1)
except Exception as e:
print("Error: Failed to rename branch: {}".format(e), file=sys.stderr)
sys.exit(1)
print("Branch '{}' renamed to '{}' on table '{}'.".format(
args.from_branch, args.to_branch, identifier))
def cmd_branch_fast_forward(args):
"""Execute ``branch fast-forward``."""
catalog, identifier = _open_catalog(args)
try:
catalog.fast_forward(identifier, args.branch_name)
except TableNotExistException:
print("Error: Table '{}' does not exist.".format(identifier),
file=sys.stderr)
sys.exit(1)
except BranchNotExistException:
print("Error: Branch '{}' does not exist.".format(args.branch_name),
file=sys.stderr)
sys.exit(1)
except Exception as e:
print("Error: Failed to fast-forward: {}".format(e), file=sys.stderr)
sys.exit(1)
print("Fast-forwarded table '{}' to branch '{}'.".format(
identifier, args.branch_name))
def add_branch_subcommands(subparsers):
"""Register the top-level ``branch <command>`` command.
Args:
subparsers: The subparsers object from the main argument parser.
"""
branch_parser = subparsers.add_parser('branch', help='Branch operations')
branch_subparsers = branch_parser.add_subparsers(
dest='branch_command', help='Branch commands')
# branch create
create_parser = branch_subparsers.add_parser(
'create', help='Create a branch on a table')
create_parser.add_argument(
'table', help='Table identifier in format: database.table')
create_parser.add_argument(
'branch_name', help='Name of the branch to create')
create_parser.add_argument(
'--tag', '-t', default=None,
help='Create the branch from this tag (default: current state)')
create_parser.set_defaults(func=cmd_branch_create)
# branch list
list_parser = branch_subparsers.add_parser(
'list', help='List branches of a table')
list_parser.add_argument(
'table', help='Table identifier in format: database.table')
list_parser.add_argument(
'--format', '-f', choices=['table', 'json'], default='table',
help='Output format: table (default) or json')
list_parser.set_defaults(func=cmd_branch_list)
# branch delete
delete_parser = branch_subparsers.add_parser(
'delete', help='Delete a branch from a table')
delete_parser.add_argument(
'table', help='Table identifier in format: database.table')
delete_parser.add_argument(
'branch_name', help='Name of the branch to delete')
delete_parser.set_defaults(func=cmd_branch_delete)
# branch rename
rename_parser = branch_subparsers.add_parser(
'rename', help='Rename a branch')
rename_parser.add_argument(
'table', help='Table identifier in format: database.table')
rename_parser.add_argument('from_branch', help='Current branch name')
rename_parser.add_argument('to_branch', help='New branch name')
rename_parser.set_defaults(func=cmd_branch_rename)
# branch fast-forward
ff_parser = branch_subparsers.add_parser(
'fast-forward', help='Fast-forward main to a branch')
ff_parser.add_argument(
'table', help='Table identifier in format: database.table')
ff_parser.add_argument(
'branch_name', help='Name of the branch to fast-forward to')
ff_parser.set_defaults(func=cmd_branch_fast_forward)