starrocks-br 0.5.2__py3-none-any.whl → 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,9 +14,20 @@
14
14
 
15
15
  import click
16
16
 
17
+ from . import config as config_module
17
18
  from . import exceptions
18
19
 
19
20
 
21
+ def _get_ops_database_name(config_path: str | None) -> str:
22
+ if not config_path:
23
+ return "ops"
24
+ try:
25
+ cfg = config_module.load_config(config_path)
26
+ return config_module.get_ops_database(cfg)
27
+ except Exception:
28
+ return "ops"
29
+
30
+
20
31
  def display_structured_error(
21
32
  title: str,
22
33
  reason: str,
@@ -74,13 +85,14 @@ def handle_missing_option_error(exc: exceptions.MissingOptionError, config: str
74
85
  def handle_backup_label_not_found_error(
75
86
  exc: exceptions.BackupLabelNotFoundError, config: str = None
76
87
  ) -> None:
88
+ ops_db = _get_ops_database_name(config)
77
89
  display_structured_error(
78
90
  title="RESTORE FAILED",
79
91
  reason=f'The backup label "{exc.label}" does not exist in the repository'
80
92
  + (f' "{exc.repository}"' if exc.repository else "")
81
93
  + ",\nor the backup did not complete successfully.",
82
94
  what_to_do=[
83
- "List available backups by querying the backup history table:\n SELECT label, backup_type, status, finished_at FROM ops.backup_history ORDER BY finished_at DESC;",
95
+ f"List available backups by querying the backup history table:\n SELECT label, backup_type, status, finished_at FROM {ops_db}.backup_history ORDER BY finished_at DESC;",
84
96
  "Check whether the backup completed successfully using StarRocks SQL:"
85
97
  + (
86
98
  f"\n SHOW BACKUP FROM `{exc.repository}`;"
@@ -97,11 +109,12 @@ def handle_backup_label_not_found_error(
97
109
  def handle_no_successful_full_backup_found_error(
98
110
  exc: exceptions.NoSuccessfulFullBackupFoundError, config: str = None
99
111
  ) -> None:
112
+ ops_db = _get_ops_database_name(config)
100
113
  display_structured_error(
101
114
  title="RESTORE FAILED",
102
115
  reason=f'No successful full backup was found before the incremental backup "{exc.incremental_label}".\nIncremental backups require a base full backup to restore from.',
103
116
  what_to_do=[
104
- "Verify that a full backup was created before this incremental backup:\n SELECT label, backup_type, status, finished_at FROM ops.backup_history WHERE backup_type = 'full' AND status = 'FINISHED' ORDER BY finished_at DESC;",
117
+ f"Verify that a full backup was created before this incremental backup:\n SELECT label, backup_type, status, finished_at FROM {ops_db}.backup_history WHERE backup_type = 'full' AND status = 'FINISHED' ORDER BY finished_at DESC;",
105
118
  "Run a full backup first:\n starrocks-br backup full --config "
106
119
  + (config if config else "<config.yaml>")
107
120
  + " --group <group_name>",
@@ -115,13 +128,14 @@ def handle_no_successful_full_backup_found_error(
115
128
  def handle_table_not_found_in_backup_error(
116
129
  exc: exceptions.TableNotFoundInBackupError, config: str = None
117
130
  ) -> None:
131
+ ops_db = _get_ops_database_name(config)
118
132
  display_structured_error(
119
133
  title="TABLE NOT FOUND",
120
134
  reason=f'Table "{exc.table}" was not found in backup "{exc.label}" for database "{exc.database}".',
121
135
  what_to_do=[
122
136
  "List all tables in the backup:"
123
137
  + (
124
- f"\n SELECT DISTINCT database_name, table_name FROM ops.backup_partitions WHERE label = '{exc.label}';"
138
+ f"\n SELECT DISTINCT database_name, table_name FROM {ops_db}.backup_partitions WHERE label = '{exc.label}';"
125
139
  if config
126
140
  else ""
127
141
  ),
@@ -201,13 +215,14 @@ def handle_cluster_health_check_failed_error(
201
215
  def handle_snapshot_not_found_error(
202
216
  exc: exceptions.SnapshotNotFoundError, config: str = None
203
217
  ) -> None:
218
+ ops_db = _get_ops_database_name(config)
204
219
  display_structured_error(
205
220
  title="SNAPSHOT NOT FOUND",
206
221
  reason=f'Snapshot "{exc.snapshot_name}" was not found in repository "{exc.repository}".',
207
222
  what_to_do=[
208
223
  f"List available snapshots:\n SHOW SNAPSHOT ON {exc.repository};",
209
224
  "Verify the snapshot name spelling is correct",
210
- "Ensure the backup completed successfully:\n SELECT * FROM ops.backup_history WHERE label = '"
225
+ f"Ensure the backup completed successfully:\n SELECT * FROM {ops_db}.backup_history WHERE label = '"
211
226
  + exc.snapshot_name
212
227
  + "';",
213
228
  ],
@@ -219,13 +234,14 @@ def handle_snapshot_not_found_error(
219
234
  def handle_no_partitions_found_error(
220
235
  exc: exceptions.NoPartitionsFoundError, config: str = None, group: str = None
221
236
  ) -> None:
237
+ ops_db = _get_ops_database_name(config)
222
238
  display_structured_error(
223
239
  title="NO PARTITIONS FOUND",
224
240
  reason="No partitions were found to backup"
225
241
  + (f" for group '{exc.group_name}'" if exc.group_name else "")
226
242
  + ".",
227
243
  what_to_do=[
228
- "Verify that the inventory group exists in ops.table_inventory:\n SELECT * FROM ops.table_inventory WHERE inventory_group = "
244
+ f"Verify that the inventory group exists in {ops_db}.table_inventory:\n SELECT * FROM {ops_db}.table_inventory WHERE inventory_group = "
229
245
  + (f"'{exc.group_name}';" if exc.group_name else "'<your_group>';"),
230
246
  "Check that the tables in the group have partitions",
231
247
  "Ensure the baseline backup date is correct",
@@ -238,6 +254,7 @@ def handle_no_partitions_found_error(
238
254
  def handle_no_tables_found_error(
239
255
  exc: exceptions.NoTablesFoundError, config: str = None, target_label: str = None
240
256
  ) -> None:
257
+ ops_db = _get_ops_database_name(config)
241
258
  display_structured_error(
242
259
  title="NO TABLES FOUND",
243
260
  reason="No tables were found"
@@ -250,12 +267,12 @@ def handle_no_tables_found_error(
250
267
  )
251
268
  + ".",
252
269
  what_to_do=[
253
- "Verify that tables exist in the backup manifest:\n SELECT DISTINCT database_name, table_name FROM ops.backup_partitions WHERE label = "
270
+ f"Verify that tables exist in the backup manifest:\n SELECT DISTINCT database_name, table_name FROM {ops_db}.backup_partitions WHERE label = "
254
271
  + (f"'{exc.label}';" if exc.label else "'<label>';"),
255
- "Check that the group name is correct in ops.table_inventory"
272
+ f"Check that the group name is correct in {ops_db}.table_inventory"
256
273
  if exc.group
257
274
  else "Verify the backup completed successfully",
258
- "List available backups:\n SELECT label, backup_type, status, finished_at FROM ops.backup_history ORDER BY finished_at DESC;",
275
+ f"List available backups:\n SELECT label, backup_type, status, finished_at FROM {ops_db}.backup_history ORDER BY finished_at DESC;",
259
276
  ],
260
277
  inputs={
261
278
  "--target-label": exc.label or target_label,
@@ -282,6 +299,7 @@ def handle_restore_operation_cancelled_error() -> None:
282
299
  def handle_concurrency_conflict_error(
283
300
  exc: exceptions.ConcurrencyConflictError, config: str = None
284
301
  ) -> None:
302
+ ops_db = _get_ops_database_name(config)
285
303
  active_job_strings = [f"{job[0]}:{job[1]}" for job in exc.active_jobs]
286
304
  first_label = exc.active_labels[0] if exc.active_labels else "unknown"
287
305
 
@@ -290,8 +308,8 @@ def handle_concurrency_conflict_error(
290
308
  reason=f"Another '{exc.scope}' job is already running.\nOnly one job of the same type can run at a time to prevent conflicts.",
291
309
  what_to_do=[
292
310
  f"Wait for the active job to complete: {', '.join(active_job_strings)}",
293
- f"Check the job status in ops.run_status:\n SELECT * FROM ops.run_status WHERE label = '{first_label}' AND state = 'ACTIVE';",
294
- f"If the job is stuck, cancel it manually:\n UPDATE ops.run_status SET state = 'CANCELLED' WHERE label = '{first_label}' AND state = 'ACTIVE';",
311
+ f"Check the job status in {ops_db}.run_status:\n SELECT * FROM {ops_db}.run_status WHERE label = '{first_label}' AND state = 'ACTIVE';",
312
+ f"If the job is stuck, cancel it manually:\n UPDATE {ops_db}.run_status SET state = 'CANCELLED' WHERE label = '{first_label}' AND state = 'ACTIVE';",
295
313
  "Verify the job is not actually running in StarRocks before cancelling it",
296
314
  ],
297
315
  inputs={
@@ -299,13 +317,14 @@ def handle_concurrency_conflict_error(
299
317
  "Scope": exc.scope,
300
318
  "Active jobs": ", ".join(active_job_strings),
301
319
  },
302
- help_links=["Check ops.run_status table for job status"],
320
+ help_links=[f"Check {ops_db}.run_status table for job status"],
303
321
  )
304
322
 
305
323
 
306
324
  def handle_no_full_backup_found_error(
307
325
  exc: exceptions.NoFullBackupFoundError, config: str = None, group: str = None
308
326
  ) -> None:
327
+ ops_db = _get_ops_database_name(config)
309
328
  display_structured_error(
310
329
  title="NO FULL BACKUP FOUND",
311
330
  reason=f"No successful full backup was found for database '{exc.database}'.\nIncremental backups require a baseline full backup to compare against.",
@@ -313,9 +332,37 @@ def handle_no_full_backup_found_error(
313
332
  "Run a full backup first:\n starrocks-br backup full --config "
314
333
  + (config if config else "<config.yaml>")
315
334
  + f" --group {group if group else '<group_name>'}",
316
- f"Verify no full backups exist for this database:\n SELECT label, backup_type, status, finished_at FROM ops.backup_history WHERE backup_type = 'full' AND label LIKE '{exc.database}_%' ORDER BY finished_at DESC;",
335
+ f"Verify no full backups exist for this database:\n SELECT label, backup_type, status, finished_at FROM {ops_db}.backup_history WHERE backup_type = 'full' AND label LIKE '{exc.database}_%' ORDER BY finished_at DESC;",
317
336
  "After the full backup completes successfully, retry the incremental backup",
318
337
  ],
319
338
  inputs={"Database": exc.database, "--config": config, "--group": group},
320
339
  help_links=["starrocks-br backup full --help"],
321
340
  )
341
+
342
+
343
+ def handle_invalid_tables_in_inventory_error(
344
+ exc: exceptions.InvalidTablesInInventoryError, config: str = None
345
+ ) -> None:
346
+ ops_db = _get_ops_database_name(config)
347
+ invalid_tables_str = ", ".join(f"'{t}'" for t in exc.invalid_tables)
348
+
349
+ display_structured_error(
350
+ title="INVALID TABLES IN INVENTORY",
351
+ reason=f"The following table(s) in the inventory do not exist in database '{exc.database}':\n{invalid_tables_str}\n\nThese tables are referenced in the table inventory but cannot be found in the actual database.",
352
+ what_to_do=[
353
+ f"Remove invalid tables from the table inventory:\n DELETE FROM {ops_db}.table_inventory WHERE database_name = '{exc.database}' AND table_name IN ({invalid_tables_str});",
354
+ "Verify the table names are spelled correctly in the inventory",
355
+ f"Check which tables exist in the database:\n SHOW TABLES FROM `{exc.database}`;",
356
+ f"Update the inventory with correct table names:\n UPDATE {ops_db}.table_inventory SET table_name = '<correct_name>' WHERE database_name = '{exc.database}' AND table_name = '<wrong_name>';",
357
+ ],
358
+ inputs={
359
+ "Database": exc.database,
360
+ "Invalid tables": invalid_tables_str,
361
+ "Group": exc.group,
362
+ "--config": config,
363
+ },
364
+ help_links=[
365
+ f"Check {ops_db}.table_inventory for your inventory configuration",
366
+ "Run 'SHOW TABLES' to see available tables",
367
+ ],
368
+ )
@@ -122,3 +122,17 @@ class NoFullBackupFoundError(StarRocksBRError):
122
122
  def __init__(self, database: str):
123
123
  self.database = database
124
124
  super().__init__(f"No successful full backup found for database '{database}'")
125
+
126
+
127
+ class InvalidTablesInInventoryError(StarRocksBRError):
128
+ def __init__(self, database: str, invalid_tables: list[str], group: str = None):
129
+ self.database = database
130
+ self.invalid_tables = invalid_tables
131
+ self.group = group
132
+ tables_str = ", ".join(f"'{t}'" for t in invalid_tables)
133
+ if group:
134
+ super().__init__(
135
+ f"Invalid tables in inventory group '{group}' for database '{database}': {tables_str}"
136
+ )
137
+ else:
138
+ super().__init__(f"Invalid tables for database '{database}': {tables_str}")
starrocks_br/executor.py CHANGED
@@ -181,6 +181,7 @@ def execute_backup(
181
181
  backup_type: Literal["incremental", "full"] = None,
182
182
  scope: str = "backup",
183
183
  database: str | None = None,
184
+ ops_database: str = "ops",
184
185
  ) -> dict:
185
186
  """Execute a complete backup workflow: submit command and monitor progress.
186
187
 
@@ -193,6 +194,7 @@ def execute_backup(
193
194
  backup_type: Type of backup (for logging)
194
195
  scope: Job scope (for concurrency control)
195
196
  database: Database name (required for SHOW BACKUP)
197
+ ops_database: Name of ops database (default: "ops")
196
198
 
197
199
  Returns dictionary with keys: success, final_status, error_message
198
200
  """
@@ -233,13 +235,18 @@ def execute_backup(
233
235
  "finished_at": finished_at,
234
236
  "error_message": None if success else (final_status["state"] or ""),
235
237
  },
238
+ ops_database=ops_database,
236
239
  )
237
240
  except Exception:
238
241
  pass
239
242
 
240
243
  try:
241
244
  concurrency.complete_job_slot(
242
- db, scope=scope, label=label, final_state=final_status["state"]
245
+ db,
246
+ scope=scope,
247
+ label=label,
248
+ final_state=final_status["state"],
249
+ ops_database=ops_database,
243
250
  )
244
251
  except Exception:
245
252
  pass
@@ -271,7 +278,7 @@ def _build_error_message(final_status: dict, label: str, database: str) -> str:
271
278
  f"Backup tracking lost for '{label}' in database '{database}'. "
272
279
  f"Another backup operation overwrote the last backup status visible in SHOW BACKUP. "
273
280
  f"This indicates a concurrency issue - only one backup per database should run at a time. "
274
- f"Recommendation: Use ops.run_status concurrency control to prevent simultaneous backups, "
281
+ f"Recommendation: Use run_status concurrency control to prevent simultaneous backups, "
275
282
  f"or verify if another tool/user is running backups on this database."
276
283
  )
277
284
  elif state == "CANCELLED":
starrocks_br/history.py CHANGED
@@ -15,8 +15,8 @@
15
15
  from . import logger
16
16
 
17
17
 
18
- def log_backup(db, entry: dict[str, str | None]) -> None:
19
- """Write a backup history entry to ops.backup_history.
18
+ def log_backup(db, entry: dict[str, str | None], ops_database: str = "ops") -> None:
19
+ """Write a backup history entry to the backup_history table.
20
20
 
21
21
  Expected keys in entry:
22
22
  - job_id (optional; auto-generated if missing)
@@ -42,7 +42,7 @@ def log_backup(db, entry: dict[str, str | None]) -> None:
42
42
  return "'" + str(val).replace("'", "''") + "'"
43
43
 
44
44
  sql = f"""
45
- INSERT INTO ops.backup_history (
45
+ INSERT INTO {ops_database}.backup_history (
46
46
  label, backup_type, status, repository, started_at, finished_at, error_message
47
47
  ) VALUES (
48
48
  {esc(label)}, {esc(backup_type)}, {esc(status)}, {esc(repository)},
@@ -57,8 +57,8 @@ def log_backup(db, entry: dict[str, str | None]) -> None:
57
57
  raise
58
58
 
59
59
 
60
- def log_restore(db, entry: dict[str, str | None]) -> None:
61
- """Write a restore history entry to ops.restore_history.
60
+ def log_restore(db, entry: dict[str, str | None], ops_database: str = "ops") -> None:
61
+ """Write a restore history entry to the restore_history table.
62
62
 
63
63
  Expected keys in entry:
64
64
  - job_id
@@ -87,12 +87,12 @@ def log_restore(db, entry: dict[str, str | None]) -> None:
87
87
  return "'" + str(val).replace("'", "''") + "'"
88
88
 
89
89
  sql = f"""
90
- INSERT INTO ops.restore_history (
91
- job_id, backup_label, restore_type, status, repository,
90
+ INSERT INTO {ops_database}.restore_history (
91
+ job_id, backup_label, restore_type, status, repository,
92
92
  started_at, finished_at, error_message, verification_checksum
93
93
  ) VALUES (
94
- {esc(job_id)}, {esc(backup_label)}, {esc(restore_type)}, {esc(status)},
95
- {esc(repository)}, {esc(started_at)}, {esc(finished_at)},
94
+ {esc(job_id)}, {esc(backup_label)}, {esc(restore_type)}, {esc(status)},
95
+ {esc(repository)}, {esc(started_at)}, {esc(finished_at)},
96
96
  {esc(error_message)}, {esc(verification_checksum)}
97
97
  )
98
98
  """
starrocks_br/labels.py CHANGED
@@ -21,12 +21,13 @@ def determine_backup_label(
21
21
  backup_type: Literal["incremental", "full"],
22
22
  database_name: str,
23
23
  custom_name: str | None = None,
24
+ ops_database: str = "ops",
24
25
  ) -> str:
25
26
  """Determine a unique backup label for the given parameters.
26
27
 
27
28
  This is the single entry point for all backup label generation. It handles both
28
29
  custom names and auto-generated date-based labels, ensuring uniqueness by checking
29
- the ops.backup_history table.
30
+ the backup_history table in the configured ops database.
30
31
 
31
32
  Args:
32
33
  db: Database connection
@@ -34,6 +35,7 @@ def determine_backup_label(
34
35
  database_name: Name of the database being backed up
35
36
  custom_name: Optional custom name for the backup. If provided, this becomes
36
37
  the base label. If None, generates a date-based label.
38
+ ops_database: Name of the database containing operational tables. Defaults to "ops".
37
39
 
38
40
  Returns:
39
41
  Unique label string that doesn't conflict with existing backups
@@ -44,9 +46,9 @@ def determine_backup_label(
44
46
  today = datetime.now().strftime("%Y%m%d")
45
47
  base_label = f"{database_name}_{today}_{backup_type}"
46
48
 
47
- query = """
49
+ query = f"""
48
50
  SELECT label
49
- FROM ops.backup_history
51
+ FROM {ops_database}.backup_history
50
52
  WHERE label LIKE %s
51
53
  ORDER BY label
52
54
  """
starrocks_br/planner.py CHANGED
@@ -18,7 +18,7 @@ import hashlib
18
18
  from starrocks_br import exceptions, logger, timezone, utils
19
19
 
20
20
 
21
- def find_latest_full_backup(db, database: str) -> dict[str, str] | None:
21
+ def find_latest_full_backup(db, database: str, ops_database: str = "ops") -> dict[str, str] | None:
22
22
  """Find the latest successful full backup for a database.
23
23
 
24
24
  Args:
@@ -31,7 +31,7 @@ def find_latest_full_backup(db, database: str) -> dict[str, str] | None:
31
31
  """
32
32
  query = f"""
33
33
  SELECT label, backup_type, finished_at
34
- FROM ops.backup_history
34
+ FROM {ops_database}.backup_history
35
35
  WHERE backup_type = 'full'
36
36
  AND status = 'FINISHED'
37
37
  AND label LIKE {utils.quote_value(f"{database}_%")}
@@ -56,7 +56,7 @@ def find_latest_full_backup(db, database: str) -> dict[str, str] | None:
56
56
  return {"label": row[0], "backup_type": row[1], "finished_at": finished_at}
57
57
 
58
58
 
59
- def find_tables_by_group(db, group_name: str) -> list[dict[str, str]]:
59
+ def find_tables_by_group(db, group_name: str, ops_database: str = "ops") -> list[dict[str, str]]:
60
60
  """Find tables belonging to a specific inventory group.
61
61
 
62
62
  Returns list of dictionaries with keys: database, table.
@@ -64,7 +64,7 @@ def find_tables_by_group(db, group_name: str) -> list[dict[str, str]]:
64
64
  """
65
65
  query = f"""
66
66
  SELECT database_name, table_name
67
- FROM ops.table_inventory
67
+ FROM {ops_database}.table_inventory
68
68
  WHERE inventory_group = {utils.quote_value(group_name)}
69
69
  ORDER BY database_name, table_name
70
70
  """
@@ -72,8 +72,49 @@ def find_tables_by_group(db, group_name: str) -> list[dict[str, str]]:
72
72
  return [{"database": row[0], "table": row[1]} for row in rows]
73
73
 
74
74
 
75
+ def validate_tables_exist(
76
+ db, database: str, tables: list[dict[str, str]], group: str = None
77
+ ) -> None:
78
+ """Validate that tables in the inventory actually exist in the database.
79
+
80
+ Args:
81
+ db: Database connection
82
+ database: Database name to validate tables against
83
+ tables: List of tables with keys: database, table
84
+ group: Optional inventory group name for better error messages
85
+
86
+ Raises:
87
+ InvalidTablesInInventoryError: If any tables don't exist in the database
88
+ """
89
+ if not tables:
90
+ return
91
+
92
+ db_tables = [t for t in tables if t["database"] == database and t["table"] != "*"]
93
+
94
+ if not db_tables:
95
+ return
96
+
97
+ show_tables_query = f"SHOW TABLES FROM {utils.quote_identifier(database)}"
98
+ existing_tables_rows = db.query(show_tables_query)
99
+ existing_tables = {row[0] for row in existing_tables_rows}
100
+
101
+ invalid_tables = []
102
+ for table_entry in db_tables:
103
+ table_name = table_entry["table"]
104
+ if table_name not in existing_tables:
105
+ invalid_tables.append(table_name)
106
+
107
+ if invalid_tables:
108
+ raise exceptions.InvalidTablesInInventoryError(database, invalid_tables, group)
109
+
110
+
75
111
  def find_recent_partitions(
76
- db, database: str, baseline_backup_label: str | None = None, *, group_name: str
112
+ db,
113
+ database: str,
114
+ baseline_backup_label: str | None = None,
115
+ *,
116
+ group_name: str,
117
+ ops_database: str = "ops",
77
118
  ) -> list[dict[str, str]]:
78
119
  """Find partitions updated since baseline for tables in the given inventory group.
79
120
 
@@ -91,7 +132,7 @@ def find_recent_partitions(
91
132
  if baseline_backup_label:
92
133
  baseline_query = f"""
93
134
  SELECT finished_at
94
- FROM ops.backup_history
135
+ FROM {ops_database}.backup_history
95
136
  WHERE label = {utils.quote_value(baseline_backup_label)}
96
137
  AND status = 'FINISHED'
97
138
  """
@@ -100,7 +141,7 @@ def find_recent_partitions(
100
141
  raise exceptions.BackupLabelNotFoundError(baseline_backup_label)
101
142
  baseline_time_raw = baseline_rows[0][0]
102
143
  else:
103
- latest_backup = find_latest_full_backup(db, database)
144
+ latest_backup = find_latest_full_backup(db, database, ops_database)
104
145
  if not latest_backup:
105
146
  raise exceptions.NoFullBackupFoundError(database)
106
147
  baseline_time_raw = latest_backup["finished_at"]
@@ -114,7 +155,7 @@ def find_recent_partitions(
114
155
 
115
156
  baseline_dt = timezone.parse_datetime_with_tz(baseline_time_str, cluster_tz)
116
157
 
117
- group_tables = find_tables_by_group(db, group_name)
158
+ group_tables = find_tables_by_group(db, group_name, ops_database)
118
159
 
119
160
  if not group_tables:
120
161
  return []
@@ -218,7 +259,7 @@ def build_incremental_backup_command(
218
259
 
219
260
 
220
261
  def build_full_backup_command(
221
- db, group_name: str, repository: str, label: str, database: str
262
+ db, group_name: str, repository: str, label: str, database: str, ops_database: str = "ops"
222
263
  ) -> str:
223
264
  """Build BACKUP command for an inventory group.
224
265
 
@@ -226,7 +267,7 @@ def build_full_backup_command(
226
267
  simple BACKUP DATABASE command. Otherwise, generate ON (TABLE ...) list for
227
268
  the specific tables within the database.
228
269
  """
229
- tables = find_tables_by_group(db, group_name)
270
+ tables = find_tables_by_group(db, group_name, ops_database)
230
271
 
231
272
  db_entries = [t for t in tables if t["database"] == database]
232
273
  if not db_entries:
@@ -245,8 +286,10 @@ def build_full_backup_command(
245
286
  ON ({on_clause})"""
246
287
 
247
288
 
248
- def record_backup_partitions(db, label: str, partitions: list[dict[str, str]]) -> None:
249
- """Record partition metadata for a backup in ops.backup_partitions table.
289
+ def record_backup_partitions(
290
+ db, label: str, partitions: list[dict[str, str]], ops_database: str = "ops"
291
+ ) -> None:
292
+ """Record partition metadata for a backup in the backup_partitions table.
250
293
 
251
294
  Args:
252
295
  db: Database connection
@@ -263,7 +306,7 @@ def record_backup_partitions(db, label: str, partitions: list[dict[str, str]]) -
263
306
  key_hash = hashlib.md5(composite_key.encode("utf-8")).hexdigest()
264
307
 
265
308
  db.execute(f"""
266
- INSERT INTO ops.backup_partitions
309
+ INSERT INTO {ops_database}.backup_partitions
267
310
  (key_hash, label, database_name, table_name, partition_name)
268
311
  VALUES ({utils.quote_value(key_hash)}, {utils.quote_value(label)}, {utils.quote_value(partition["database"])}, {utils.quote_value(partition["table"])}, {utils.quote_value(partition["partition_name"])})
269
312
  """)
@@ -30,7 +30,7 @@ def ensure_repository(db, name: str) -> None:
30
30
  raise RuntimeError(
31
31
  f"Repository '{name}' not found. Please create it first using:\n"
32
32
  f" CREATE REPOSITORY {name} WITH BROKER ON LOCATION '...' PROPERTIES(...)\n"
33
- f"For examples, see: https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/backup_restore/CREATE_REPOSITORY/"
33
+ f"For examples, see: https://docs.starrocks.io/docs/sql-reference/sql-statements/backup_restore/CREATE_REPOSITORY/"
34
34
  )
35
35
 
36
36
  # SHOW REPOSITORIES returns: RepoId, RepoName, CreateTime, IsReadOnly, Location, Broker, ErrMsg