starrocks-br 0.5.1__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.
@@ -1,3 +1,17 @@
1
+ # Copyright 2025 deep-bi
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  from __future__ import annotations
2
16
 
3
17
 
@@ -16,7 +30,7 @@ def ensure_repository(db, name: str) -> None:
16
30
  raise RuntimeError(
17
31
  f"Repository '{name}' not found. Please create it first using:\n"
18
32
  f" CREATE REPOSITORY {name} WITH BROKER ON LOCATION '...' PROPERTIES(...)\n"
19
- 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/"
20
34
  )
21
35
 
22
36
  # SHOW REPOSITORIES returns: RepoId, RepoName, CreateTime, IsReadOnly, Location, Broker, ErrMsg
starrocks_br/restore.py CHANGED
@@ -1,3 +1,17 @@
1
+ # Copyright 2025 deep-bi
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  import datetime
2
16
  import time
3
17
 
@@ -187,9 +201,22 @@ def execute_restore(
187
201
  max_polls: int = MAX_POLLS,
188
202
  poll_interval: float = 1.0,
189
203
  scope: str = "restore",
204
+ ops_database: str = "ops",
190
205
  ) -> dict:
191
206
  """Execute a complete restore workflow: submit command and monitor progress.
192
207
 
208
+ Args:
209
+ db: Database connection
210
+ restore_command: Restore SQL command to execute
211
+ backup_label: Label of the backup being restored
212
+ restore_type: Type of restore operation
213
+ repository: Repository name
214
+ database: Database name
215
+ max_polls: Maximum polling attempts
216
+ poll_interval: Seconds between polls
217
+ scope: Job scope (for concurrency control)
218
+ ops_database: Name of ops database (default: "ops")
219
+
193
220
  Returns dictionary with keys: success, final_status, error_message
194
221
  """
195
222
  cluster_tz = db.timezone
@@ -226,13 +253,18 @@ def execute_restore(
226
253
  "finished_at": finished_at,
227
254
  "error_message": None if success else final_status["state"],
228
255
  },
256
+ ops_database=ops_database,
229
257
  )
230
258
  except Exception as e:
231
259
  logger.error(f"Failed to log restore history: {str(e)}")
232
260
 
233
261
  try:
234
262
  concurrency.complete_job_slot(
235
- db, scope=scope, label=label, final_state=final_status["state"]
263
+ db,
264
+ scope=scope,
265
+ label=label,
266
+ final_state=final_status["state"],
267
+ ops_database=ops_database,
236
268
  )
237
269
  except Exception as e:
238
270
  logger.error(f"Failed to complete job slot: {str(e)}")
@@ -250,7 +282,7 @@ def execute_restore(
250
282
  return {"success": False, "final_status": None, "error_message": str(e)}
251
283
 
252
284
 
253
- def find_restore_pair(db, target_label: str) -> list[str]:
285
+ def find_restore_pair(db, target_label: str, ops_database: str = "ops") -> list[str]:
254
286
  """Find the correct sequence of backups needed for restore.
255
287
 
256
288
  Args:
@@ -266,7 +298,7 @@ def find_restore_pair(db, target_label: str) -> list[str]:
266
298
  """
267
299
  query = f"""
268
300
  SELECT label, backup_type, finished_at
269
- FROM ops.backup_history
301
+ FROM {ops_database}.backup_history
270
302
  WHERE label = {utils.quote_value(target_label)}
271
303
  AND status = 'FINISHED'
272
304
  """
@@ -285,7 +317,7 @@ def find_restore_pair(db, target_label: str) -> list[str]:
285
317
 
286
318
  full_backup_query = f"""
287
319
  SELECT label, backup_type, finished_at
288
- FROM ops.backup_history
320
+ FROM {ops_database}.backup_history
289
321
  WHERE backup_type = 'full'
290
322
  AND status = 'FINISHED'
291
323
  AND label LIKE {utils.quote_value(f"{database_name}_%")}
@@ -312,6 +344,7 @@ def get_tables_from_backup(
312
344
  group: str | None = None,
313
345
  table: str | None = None,
314
346
  database: str | None = None,
347
+ ops_database: str = "ops",
315
348
  ) -> list[str]:
316
349
  """Get list of tables to restore from backup manifest.
317
350
 
@@ -340,7 +373,7 @@ def get_tables_from_backup(
340
373
 
341
374
  query = f"""
342
375
  SELECT DISTINCT database_name, table_name
343
- FROM ops.backup_partitions
376
+ FROM {ops_database}.backup_partitions
344
377
  WHERE label = {utils.quote_value(label)}
345
378
  ORDER BY database_name, table_name
346
379
  """
@@ -363,7 +396,7 @@ def get_tables_from_backup(
363
396
  if group:
364
397
  group_query = f"""
365
398
  SELECT database_name, table_name
366
- FROM ops.table_inventory
399
+ FROM {ops_database}.table_inventory
367
400
  WHERE inventory_group = {utils.quote_value(group)}
368
401
  """
369
402
 
@@ -390,6 +423,35 @@ def get_tables_from_backup(
390
423
  return tables
391
424
 
392
425
 
426
+ def get_partitions_from_backup(
427
+ db, label: str, table: str, ops_database: str = "ops"
428
+ ) -> list[str]:
429
+ """Get list of partitions for a specific table from backup manifest.
430
+
431
+ Args:
432
+ db: Database connection
433
+ label: Backup label
434
+ table: Table name in format 'database.table'
435
+ ops_database: Operations database name
436
+
437
+ Returns:
438
+ List of partition names for the table in this backup
439
+ """
440
+ database_name, table_name = table.split(".", 1)
441
+
442
+ query = f"""
443
+ SELECT partition_name
444
+ FROM {ops_database}.backup_partitions
445
+ WHERE label = {utils.quote_value(label)}
446
+ AND database_name = {utils.quote_value(database_name)}
447
+ AND table_name = {utils.quote_value(table_name)}
448
+ ORDER BY partition_name
449
+ """
450
+
451
+ rows = db.query(query)
452
+ return [row[0] for row in rows]
453
+
454
+
393
455
  def execute_restore_flow(
394
456
  db,
395
457
  repo_name: str,
@@ -397,6 +459,7 @@ def execute_restore_flow(
397
459
  tables_to_restore: list[str],
398
460
  rename_suffix: str = "_restored",
399
461
  skip_confirmation: bool = False,
462
+ ops_database: str = "ops",
400
463
  ) -> dict:
401
464
  """Execute the complete restore flow with safety measures.
402
465
 
@@ -407,6 +470,7 @@ def execute_restore_flow(
407
470
  tables_to_restore: List of tables to restore (format: database.table)
408
471
  rename_suffix: Suffix for temporary tables
409
472
  skip_confirmation: If True, skip interactive confirmation prompt
473
+ ops_database: Name of ops database (default: "ops")
410
474
 
411
475
  Returns:
412
476
  Dictionary with success status and details
@@ -438,59 +502,122 @@ def execute_restore_flow(
438
502
  database_name = tables_to_restore[0].split(".")[0]
439
503
 
440
504
  base_label = restore_pair[0]
441
- logger.info("")
442
- logger.info(f"Step 1: Restoring base backup '{base_label}'...")
443
-
444
- base_timestamp = get_snapshot_timestamp(db, repo_name, base_label)
445
-
446
- base_restore_command = _build_restore_command_with_rename(
447
- base_label, repo_name, tables_to_restore, rename_suffix, database_name, base_timestamp
448
- )
449
-
450
- base_result = execute_restore(
451
- db, base_restore_command, base_label, "full", repo_name, database_name, scope="restore"
452
- )
453
-
454
- if not base_result["success"]:
455
- return {
456
- "success": False,
457
- "error_message": f"Base restore failed: {base_result['error_message']}",
458
- }
459
505
 
460
- logger.success("Base restore completed successfully")
506
+ tables_in_base = get_tables_from_backup(db, base_label, ops_database=ops_database)
507
+ tables_to_restore_from_base = [t for t in tables_to_restore if t in tables_in_base]
461
508
 
462
- if len(restore_pair) > 1:
463
- incremental_label = restore_pair[1]
509
+ if tables_to_restore_from_base:
464
510
  logger.info("")
465
- logger.info(f"Step 2: Applying incremental backup '{incremental_label}'...")
511
+ logger.info(f"Step 1: Restoring base backup '{base_label}'...")
466
512
 
467
- incremental_timestamp = get_snapshot_timestamp(db, repo_name, incremental_label)
513
+ base_timestamp = get_snapshot_timestamp(db, repo_name, base_label)
468
514
 
469
- incremental_restore_command = _build_restore_command_without_rename(
470
- incremental_label,
515
+ base_restore_command = _build_restore_command_with_rename(
516
+ base_label,
471
517
  repo_name,
472
- tables_to_restore,
518
+ tables_to_restore_from_base,
519
+ rename_suffix,
473
520
  database_name,
474
- incremental_timestamp,
521
+ base_timestamp,
475
522
  )
476
523
 
477
- incremental_result = execute_restore(
524
+ base_result = execute_restore(
478
525
  db,
479
- incremental_restore_command,
480
- incremental_label,
481
- "incremental",
526
+ base_restore_command,
527
+ base_label,
528
+ "full",
482
529
  repo_name,
483
530
  database_name,
484
531
  scope="restore",
532
+ ops_database=ops_database,
485
533
  )
486
534
 
487
- if not incremental_result["success"]:
535
+ if not base_result["success"]:
488
536
  return {
489
537
  "success": False,
490
- "error_message": f"Incremental restore failed: {incremental_result['error_message']}",
538
+ "error_message": f"Base restore failed: {base_result['error_message']}",
491
539
  }
492
540
 
493
- logger.success("Incremental restore completed successfully")
541
+ logger.success("Base restore completed successfully")
542
+ else:
543
+ logger.info("")
544
+ logger.info(
545
+ f"Step 1: Skipping base backup '{base_label}' (no requested tables in this backup)"
546
+ )
547
+
548
+ if len(restore_pair) > 1:
549
+ incremental_label = restore_pair[1]
550
+
551
+ tables_in_incremental = get_tables_from_backup(
552
+ db, incremental_label, ops_database=ops_database
553
+ )
554
+ tables_to_restore_from_incremental = [
555
+ t for t in tables_to_restore if t in tables_in_incremental
556
+ ]
557
+
558
+ if not tables_to_restore_from_incremental:
559
+ logger.info("")
560
+ logger.info(
561
+ f"Step 2: Skipping incremental backup '{incremental_label}' (no requested tables in this backup)"
562
+ )
563
+ else:
564
+ logger.info("")
565
+ logger.info(f"Step 2: Applying incremental backup '{incremental_label}'...")
566
+
567
+ incremental_timestamp = get_snapshot_timestamp(db, repo_name, incremental_label)
568
+
569
+ for table in tables_to_restore_from_incremental:
570
+ partitions = get_partitions_from_backup(
571
+ db, incremental_label, table, ops_database=ops_database
572
+ )
573
+
574
+ if not partitions:
575
+ logger.warning(f"No partitions found for {table} in {incremental_label}, skipping")
576
+ continue
577
+
578
+ table_was_in_base = table in tables_to_restore_from_base
579
+
580
+ if table_was_in_base:
581
+ _, table_name = table.split(".", 1)
582
+ target_table_name = f"{table_name}{rename_suffix}"
583
+ incremental_restore_command = _build_partition_restore_command(
584
+ incremental_label,
585
+ repo_name,
586
+ f"{database_name}.{target_table_name}",
587
+ partitions,
588
+ database_name,
589
+ incremental_timestamp,
590
+ rename_suffix=None,
591
+ )
592
+ else:
593
+ incremental_restore_command = _build_partition_restore_command(
594
+ incremental_label,
595
+ repo_name,
596
+ table,
597
+ partitions,
598
+ database_name,
599
+ incremental_timestamp,
600
+ rename_suffix=rename_suffix,
601
+ )
602
+
603
+ incremental_result = execute_restore(
604
+ db,
605
+ incremental_restore_command,
606
+ incremental_label,
607
+ "incremental",
608
+ repo_name,
609
+ database_name,
610
+ scope="restore",
611
+ ops_database=ops_database,
612
+ )
613
+
614
+ if not incremental_result["success"]:
615
+ return {
616
+ "success": False,
617
+ "error_message": f"Incremental restore failed for {table}: {incremental_result['error_message']}",
618
+ }
619
+
620
+ logger.success("Incremental restore completed successfully")
494
621
 
495
622
  logger.info("")
496
623
  logger.info("Step 3: Performing atomic rename...")
@@ -557,6 +684,50 @@ def _build_restore_command_without_rename(
557
684
  PROPERTIES ("backup_timestamp" = "{backup_timestamp}")"""
558
685
 
559
686
 
687
+ def _build_partition_restore_command(
688
+ backup_label: str,
689
+ repo_name: str,
690
+ table: str,
691
+ partitions: list[str],
692
+ database: str,
693
+ backup_timestamp: str,
694
+ rename_suffix: str | None = None,
695
+ ) -> str:
696
+ """Build partition-level restore command with optional AS clause.
697
+
698
+ Args:
699
+ backup_label: Backup snapshot label
700
+ repo_name: Repository name
701
+ table: Table name in format 'database.table'
702
+ partitions: List of partition names to restore
703
+ database: Database name
704
+ backup_timestamp: Backup timestamp
705
+ rename_suffix: Optional suffix for AS clause (e.g., '_restored')
706
+
707
+ Returns:
708
+ SQL RESTORE command string
709
+ """
710
+ _, table_name = table.split(".", 1)
711
+
712
+ # Build partition list
713
+ partition_list = ", ".join([utils.quote_identifier(p) for p in partitions])
714
+
715
+ # Build table clause
716
+ if rename_suffix:
717
+ # Table only in incremental: use AS clause
718
+ temp_table_name = f"{table_name}{rename_suffix}"
719
+ table_clause = f"TABLE {utils.quote_identifier(table_name)} PARTITION ({partition_list}) AS {utils.quote_identifier(temp_table_name)}"
720
+ else:
721
+ # Table in base: target the _restored table directly (no AS)
722
+ table_clause = f"TABLE {utils.quote_identifier(table_name)} PARTITION ({partition_list})"
723
+
724
+ return f"""RESTORE SNAPSHOT {utils.quote_identifier(backup_label)}
725
+ FROM {utils.quote_identifier(repo_name)}
726
+ DATABASE {utils.quote_identifier(database)}
727
+ ON ({table_clause})
728
+ PROPERTIES ("backup_timestamp" = "{backup_timestamp}")"""
729
+
730
+
560
731
  def _generate_timestamped_backup_name(table_name: str) -> str:
561
732
  """Generate a timestamped backup table name.
562
733
 
starrocks_br/schema.py CHANGED
@@ -1,71 +1,131 @@
1
+ # Copyright 2025 deep-bi
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  from . import logger
2
16
 
3
17
 
4
- def initialize_ops_schema(db) -> None:
18
+ def initialize_ops_schema(
19
+ db, ops_database: str = "ops", table_inventory_entries: list[tuple[str, str, str]] | None = None
20
+ ) -> None:
5
21
  """Initialize the ops database and all required control tables.
6
22
 
7
- Creates empty ops tables. Does NOT populate with sample data.
8
- Users must manually insert their table inventory records.
23
+ Creates empty ops tables. Optionally populates table_inventory from entries.
24
+
25
+ Args:
26
+ db: Database connection
27
+ ops_database: Name of the ops database (defaults to "ops")
28
+ table_inventory_entries: Optional list of (group, database, table) tuples to bootstrap
9
29
  """
10
30
 
11
- logger.info("Creating ops database...")
12
- db.execute("CREATE DATABASE IF NOT EXISTS ops")
13
- logger.success("ops database created")
31
+ logger.info(f"Creating {ops_database} database...")
32
+ db.execute(f"CREATE DATABASE IF NOT EXISTS {ops_database}")
33
+ logger.success(f"{ops_database} database created")
34
+
35
+ logger.info(f"Creating {ops_database}.table_inventory...")
36
+ db.execute(get_table_inventory_schema(ops_database=ops_database))
37
+ logger.success(f"{ops_database}.table_inventory created")
14
38
 
15
- logger.info("Creating ops.table_inventory...")
16
- db.execute(get_table_inventory_schema())
17
- logger.success("ops.table_inventory created")
39
+ logger.info(f"Creating {ops_database}.backup_history...")
40
+ db.execute(get_backup_history_schema(ops_database=ops_database))
41
+ logger.success(f"{ops_database}.backup_history created")
18
42
 
19
- logger.info("Creating ops.backup_history...")
20
- db.execute(get_backup_history_schema())
21
- logger.success("ops.backup_history created")
43
+ logger.info(f"Creating {ops_database}.restore_history...")
44
+ db.execute(get_restore_history_schema(ops_database=ops_database))
45
+ logger.success(f"{ops_database}.restore_history created")
22
46
 
23
- logger.info("Creating ops.restore_history...")
24
- db.execute(get_restore_history_schema())
25
- logger.success("ops.restore_history created")
47
+ logger.info(f"Creating {ops_database}.run_status...")
48
+ db.execute(get_run_status_schema(ops_database=ops_database))
49
+ logger.success(f"{ops_database}.run_status created")
26
50
 
27
- logger.info("Creating ops.run_status...")
28
- db.execute(get_run_status_schema())
29
- logger.success("ops.run_status created")
51
+ logger.info(f"Creating {ops_database}.backup_partitions...")
52
+ db.execute(get_backup_partitions_schema(ops_database=ops_database))
53
+ logger.success(f"{ops_database}.backup_partitions created")
54
+
55
+ if table_inventory_entries:
56
+ logger.info(f"Bootstrapping {ops_database}.table_inventory from configuration...")
57
+ bootstrap_table_inventory(db, table_inventory_entries, ops_database=ops_database)
58
+ logger.success(
59
+ f"{ops_database}.table_inventory bootstrapped with {len(table_inventory_entries)} entries"
60
+ )
30
61
 
31
- logger.info("Creating ops.backup_partitions...")
32
- db.execute(get_backup_partitions_schema())
33
- logger.success("ops.backup_partitions created")
34
62
  logger.info("")
35
63
  logger.success("Schema initialized successfully!")
36
64
 
37
65
 
38
- def ensure_ops_schema(db) -> bool:
66
+ def ensure_ops_schema(db, ops_database: str = "ops") -> bool:
39
67
  """Ensure ops schema exists, creating it if necessary.
40
68
 
41
69
  Returns True if schema was created, False if it already existed.
42
70
  This is called automatically before backup/restore operations.
43
71
  """
44
72
  try:
45
- result = db.query("SHOW DATABASES LIKE 'ops'")
73
+ result = db.query(f"SHOW DATABASES LIKE '{ops_database}'")
46
74
 
47
75
  if not result:
48
- initialize_ops_schema(db)
76
+ initialize_ops_schema(db, ops_database=ops_database)
49
77
  return True
50
78
 
51
- db.execute("USE ops")
79
+ db.execute(f"USE {ops_database}")
52
80
  tables_result = db.query("SHOW TABLES")
53
81
 
54
82
  if not tables_result or len(tables_result) < 5:
55
- initialize_ops_schema(db)
83
+ initialize_ops_schema(db, ops_database=ops_database)
56
84
  return True
57
85
 
58
86
  return False
59
87
 
60
88
  except Exception:
61
- initialize_ops_schema(db)
89
+ initialize_ops_schema(db, ops_database=ops_database)
62
90
  return True
63
91
 
64
92
 
65
- def get_table_inventory_schema() -> str:
93
+ def bootstrap_table_inventory(
94
+ db, entries: list[tuple[str, str, str]], ops_database: str = "ops"
95
+ ) -> None:
96
+ """Bootstrap table_inventory table with entries from configuration.
97
+
98
+ Args:
99
+ db: Database connection
100
+ entries: List of (group, database, table) tuples
101
+ ops_database: Name of the ops database (defaults to "ops")
102
+ """
103
+ if not entries:
104
+ return
105
+
106
+ unique_databases = {database for _, database, _ in entries}
107
+
108
+ for database_name in unique_databases:
109
+ result = db.query(f"SHOW DATABASES LIKE '{database_name}'")
110
+ if not result:
111
+ logger.warning(
112
+ f"Database '{database_name}' does not exist. "
113
+ f"Table inventory entries will be created, but backups will fail until the database is created."
114
+ )
115
+
116
+ for group, database, table in entries:
117
+ sql = f"""
118
+ INSERT INTO {ops_database}.table_inventory
119
+ (inventory_group, database_name, table_name)
120
+ VALUES ('{group}', '{database}', '{table}')
121
+ """
122
+ db.execute(sql)
123
+
124
+
125
+ def get_table_inventory_schema(ops_database: str = "ops") -> str:
66
126
  """Get CREATE TABLE statement for table_inventory."""
67
- return """
68
- CREATE TABLE IF NOT EXISTS ops.table_inventory (
127
+ return f"""
128
+ CREATE TABLE IF NOT EXISTS {ops_database}.table_inventory (
69
129
  inventory_group STRING NOT NULL COMMENT "Group name for a set of tables",
70
130
  database_name STRING NOT NULL COMMENT "Database name",
71
131
  table_name STRING NOT NULL COMMENT "Table name, or '*' for all tables in database",
@@ -78,10 +138,10 @@ def get_table_inventory_schema() -> str:
78
138
  """
79
139
 
80
140
 
81
- def get_backup_history_schema() -> str:
141
+ def get_backup_history_schema(ops_database: str = "ops") -> str:
82
142
  """Get CREATE TABLE statement for backup_history."""
83
- return """
84
- CREATE TABLE IF NOT EXISTS ops.backup_history (
143
+ return f"""
144
+ CREATE TABLE IF NOT EXISTS {ops_database}.backup_history (
85
145
  label STRING NOT NULL COMMENT "Unique backup snapshot label",
86
146
  backup_type STRING NOT NULL COMMENT "Type of backup: full or incremental",
87
147
  status STRING NOT NULL COMMENT "Final backup status: FINISHED, FAILED, CANCELLED, TIMEOUT",
@@ -96,10 +156,10 @@ def get_backup_history_schema() -> str:
96
156
  """
97
157
 
98
158
 
99
- def get_restore_history_schema() -> str:
159
+ def get_restore_history_schema(ops_database: str = "ops") -> str:
100
160
  """Get CREATE TABLE statement for restore_history."""
101
- return """
102
- CREATE TABLE IF NOT EXISTS ops.restore_history (
161
+ return f"""
162
+ CREATE TABLE IF NOT EXISTS {ops_database}.restore_history (
103
163
  job_id STRING NOT NULL COMMENT "Unique restore job identifier",
104
164
  backup_label STRING NOT NULL COMMENT "Source backup snapshot label",
105
165
  restore_type STRING NOT NULL COMMENT "Type of restore: partition, table, or database",
@@ -116,10 +176,10 @@ def get_restore_history_schema() -> str:
116
176
  """
117
177
 
118
178
 
119
- def get_run_status_schema() -> str:
179
+ def get_run_status_schema(ops_database: str = "ops") -> str:
120
180
  """Get CREATE TABLE statement for run_status."""
121
- return """
122
- CREATE TABLE IF NOT EXISTS ops.run_status (
181
+ return f"""
182
+ CREATE TABLE IF NOT EXISTS {ops_database}.run_status (
123
183
  scope STRING NOT NULL COMMENT "Job scope: backup or restore",
124
184
  label STRING NOT NULL COMMENT "Job label or identifier",
125
185
  state STRING NOT NULL DEFAULT "ACTIVE" COMMENT "Job state: ACTIVE, FINISHED, FAILED, or CANCELLED",
@@ -131,12 +191,12 @@ def get_run_status_schema() -> str:
131
191
  """
132
192
 
133
193
 
134
- def get_backup_partitions_schema() -> str:
194
+ def get_backup_partitions_schema(ops_database: str = "ops") -> str:
135
195
  """Get CREATE TABLE statement for backup_partitions."""
136
- return """
137
- CREATE TABLE IF NOT EXISTS ops.backup_partitions (
196
+ return f"""
197
+ CREATE TABLE IF NOT EXISTS {ops_database}.backup_partitions (
138
198
  key_hash STRING NOT NULL COMMENT "MD5 hash of composite key (label, database_name, table_name, partition_name)",
139
- label STRING NOT NULL COMMENT "The backup label this partition belongs to. FK to ops.backup_history.label.",
199
+ label STRING NOT NULL COMMENT "The backup label this partition belongs to. FK to {ops_database}.backup_history.label.",
140
200
  database_name STRING NOT NULL COMMENT "The name of the database the partition belongs to.",
141
201
  table_name STRING NOT NULL COMMENT "The name of the table the partition belongs to.",
142
202
  partition_name STRING NOT NULL COMMENT "The name of the specific partition.",
starrocks_br/timezone.py CHANGED
@@ -1,3 +1,17 @@
1
+ # Copyright 2025 deep-bi
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  import datetime
2
16
  from zoneinfo import ZoneInfo
3
17
 
starrocks_br/utils.py CHANGED
@@ -1,3 +1,18 @@
1
+ # Copyright 2025 deep-bi
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
1
16
  def quote_identifier(identifier):
2
17
  """
3
18
  Quote a SQL identifier (database, table, or column name) with backticks.