feat: add Database.merge() and sqlite-utils merge command

Implements the ability to merge tables from one or more source SQLite
databases into a destination database, as requested in #491.

Python API:
    db.merge([src1, src2], alter=True, replace=False, ignore=False, tables=None)
  - source_dbs can be Database objects or file paths
  - Tables not in dest are created; existing tables have rows inserted
  - alter=True adds missing columns to existing destination tables
  - replace=True overwrites rows with matching primary keys
  - ignore=True skips rows with conflicting primary keys
  - tables= limits which tables are merged
  - Virtual tables and their shadow tables are automatically skipped

CLI:
    sqlite-utils merge combined.db one.db two.db [options]
  - Supports --alter, --replace, --ignore, --pk, --table, --load-extension

Closes #491
This commit is contained in:
Abhishek Yadav 2026-03-22 20:49:01 +05:30
commit 37caace215
4 changed files with 418 additions and 0 deletions

View file

@ -1541,6 +1541,63 @@ def create_database(path, enable_wal, init_spatialite, load_extension):
db.vacuum()
@cli.command(name="merge")
@click.argument(
"path",
type=click.Path(file_okay=True, dir_okay=False, allow_dash=False),
required=True,
)
@click.argument(
"sources",
type=click.Path(file_okay=True, dir_okay=False, allow_dash=False, exists=True),
nargs=-1,
required=True,
)
@click.option("pks", "--pk", help="Column to use as primary key", multiple=True)
@click.option("--alter", is_flag=True, help="Alter destination tables to add any missing columns")
@click.option(
"--replace", is_flag=True, help="Replace rows with matching primary keys"
)
@click.option(
"--ignore", is_flag=True, help="Ignore rows with conflicting primary keys"
)
@click.option(
"tables",
"--table",
help="Specific tables to merge (can be specified multiple times)",
multiple=True,
)
@load_extension_option
def merge_cmd(path, sources, pks, alter, replace, ignore, tables, load_extension):
"""
Merge tables from one or more SOURCE databases into a DEST database.
Tables that do not exist in DEST are created. Tables that already exist
have their rows inserted. Use --alter to add missing columns automatically.
Example:
\b
sqlite-utils merge combined.db one.db two.db
sqlite-utils merge combined.db one.db two.db --alter
sqlite-utils merge combined.db one.db two.db --replace --table mytable
"""
db = sqlite_utils.Database(path)
_register_db_for_cleanup(db)
_load_extensions(db, load_extension)
try:
db.merge(
sources,
pk=list(pks) if pks else None,
alter=alter,
replace=replace,
ignore=ignore,
tables=list(tables) if tables else None,
)
except OperationalError as e:
raise click.ClickException(str(e))
@cli.command(name="create-table")
@click.argument(
"path",