From 22bd78bedabfe8a924b1e44c7807227b18f81afb Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Fri, 20 Mar 2026 00:26:18 +0100 Subject: [PATCH 01/14] feat: add new command \shards [all] --- docs/commands.rst | 5 ++++ src/crate/crash/commands.py | 46 +++++++++++++++++++++++++++++++++++++ tests/test_integration.py | 2 ++ 3 files changed, 53 insertions(+) diff --git a/docs/commands.rst b/docs/commands.rst index 47434210..8afa103b 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -56,6 +56,11 @@ Every command starts with a ``\`` character. | ``\r `` | Reads statements from ```` and execute | | | them. | +------------------------+-----------------------------------------------------+ +| ``\shards [all]`` | Queries the ``sys`` tables aggregating shards status| +| | followed by filtering shards relocation and | +| | initialization, unless ``all`` is used. In which | +| | case no filter is applied. | ++------------------------+-----------------------------------------------------+ | ``\sysinfo`` | Query the ``sys`` tables for system and cluster | | | information. | +------------------------+-----------------------------------------------------+ diff --git a/src/crate/crash/commands.py b/src/crate/crash/commands.py index ca011d4e..47632523 100644 --- a/src/crate/crash/commands.py +++ b/src/crate/crash/commands.py @@ -234,6 +234,51 @@ def __call__(self, cmd, check_name=None, **kwargs): cmd.logger.warn('No check for {}'.format(check_name)) +class ShardsCommand(Command): + """ short summary for shards and their relocation (use `all` as argument for longer output) """ + + SHORT_STMT = """SELECT routing_state, state, COUNT(*) AS shard_count, SUM(num_docs) as num_docs, SUM(size) / 1073741824.0 as size_gb + FROM sys.shards + GROUP BY routing_state, state + ORDER BY shard_count DESC; + """ + + RELOC_STMT = """ + SELECT node['name'] as name, id, recovery['stage'] as stage, recovery['size']['percent'] AS "%", routing_state, state, primary, table_name, relocating_node, size / 1024 as size_kb, partition_ident + FROM sys.shards + WHERE routing_state NOT IN ('STARTED') + ORDER BY id, name; + """ + + ALLSHARDS_STMT = """ + SELECT node['name'] as name, id, recovery['stage'] as stage, recovery['size']['percent'] AS "%", routing_state, state, primary, table_name, relocating_node, size / 1024 as size_kb, partition_ident + FROM sys.shards + ORDER BY id, name; + """ + + OPTIONS = ('all',) + + def complete(self, cmd, text): + return (i for i in self.OPTIONS if i.startswith(text) or text == '') + + def execute(self, cmd, stmt): + success = cmd._exec(stmt) + cmd.exit_code = cmd.exit_code or int(not success) + if not success: + cmd.logger.warn("FAILED") + return False + + cur = cmd.cursor + shards = cur.fetchall() + if len(shards): + cmd.pprint(shards, [c[0] for c in cur.description]) + return True + + def __call__(self, cmd, *args, **kwargs): + for stmt in (self.SHORT_STMT, self.RELOC_STMT if 'all' not in args else self.ALLSHARDS_STMT): + self.execute(cmd, stmt) + + built_in_commands = { '?': HelpCommand(), 'r': ReadFileCommand(), @@ -243,4 +288,5 @@ def __call__(self, cmd, check_name=None, **kwargs): 'verbose': ToggleVerboseCommand(), 'check': CheckCommand(), 'pager': SetPager(), + 'shards': ShardsCommand(), } diff --git a/tests/test_integration.py b/tests/test_integration.py index 033c4781..e3d3734a 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -653,10 +653,12 @@ def test_help_command(self): '\\pager set an external pager. Use without argument to reset to internal paging', '\\q quit crash', '\\r read and execute statements from a file', + '\\shards short summary for shards and their relocation (use `all` as argument for longer output)', '\\sysinfo print system and cluster info', '\\verbose toggle verbose mode', ]) + help_ = command.commands['?'] self.assertTrue(isinstance(help_, Command)) self.assertEqual(expected, help_(command)) From 4146fdf002725c69667bc8e43c722db2ed4bd149 Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Sat, 21 Mar 2026 22:53:41 +0100 Subject: [PATCH 02/14] feat: change \shards [state|relocating] --- docs/commands.rst | 9 ++-- src/crate/crash/commands.py | 91 +++++++++++++++++++++++++++---------- 2 files changed, 73 insertions(+), 27 deletions(-) diff --git a/docs/commands.rst b/docs/commands.rst index 8afa103b..204ab141 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -56,10 +56,11 @@ Every command starts with a ``\`` character. | ``\r `` | Reads statements from ```` and execute | | | them. | +------------------------+-----------------------------------------------------+ -| ``\shards [all]`` | Queries the ``sys`` tables aggregating shards status| -| | followed by filtering shards relocation and | -| | initialization, unless ``all`` is used. In which | -| | case no filter is applied. | +| ``\shards [VIEW]`` | Queries ``sys.shards`` table and computes relocation| +| | progress progress per table. If ``VIEW`` is instead:| +| | | +| | - ``state`` provides aggregeration on shard state | +| | - ``relocating`` tracks which shards are relocated | +------------------------+-----------------------------------------------------+ | ``\sysinfo`` | Query the ``sys`` tables for system and cluster | | | information. | diff --git a/src/crate/crash/commands.py b/src/crate/crash/commands.py index 47632523..6a7a19b8 100644 --- a/src/crate/crash/commands.py +++ b/src/crate/crash/commands.py @@ -235,31 +235,69 @@ def __call__(self, cmd, check_name=None, **kwargs): class ShardsCommand(Command): - """ short summary for shards and their relocation (use `all` as argument for longer output) """ + """shows progress of shards relocation (optional arguments: `state` and `relocating`)""" - SHORT_STMT = """SELECT routing_state, state, COUNT(*) AS shard_count, SUM(num_docs) as num_docs, SUM(size) / 1073741824.0 as size_gb + DEFAULT_STMT = """ + SELECT + table_name, + COUNT(*) + AS total_shards, + SUM(num_docs) + AS total_num_docs, + SUM(size) + As total_sum_shard_size, + SUM(CASE WHEN routing_state = 'RELOCATING' THEN 1 ELSE 0 END) + AS relocating_shards, + SUM(CASE WHEN routing_state = 'RELOCATING' THEN size ELSE 0 END) + AS relocating_size, + 100.0 * SUM(CASE WHEN routing_state != 'RELOCATING' THEN size ELSE 0 END) / CAST(SUM(size) as DOUBLE) + AS relocated_percent FROM sys.shards - GROUP BY routing_state, state - ORDER BY shard_count DESC; + WHERE routing_state != 'UNASSIGNED' + GROUP BY table_name + ORDER BY table_name; """ - RELOC_STMT = """ - SELECT node['name'] as name, id, recovery['stage'] as stage, recovery['size']['percent'] AS "%", routing_state, state, primary, table_name, relocating_node, size / 1024 as size_kb, partition_ident + STATE_STMT = """ + SELECT + routing_state, + COUNT(*) + AS shard_count, + SUM(num_docs) + AS num_docs, + SUM(size) / 1073741824.0 + AS size_gb FROM sys.shards - WHERE routing_state NOT IN ('STARTED') - ORDER BY id, name; + GROUP BY routing_state + ORDER BY routing_state; """ - ALLSHARDS_STMT = """ - SELECT node['name'] as name, id, recovery['stage'] as stage, recovery['size']['percent'] AS "%", routing_state, state, primary, table_name, relocating_node, size / 1024 as size_kb, partition_ident + RELOC_STMT = """ + SELECT + table_name, + node['name'], + id, + recovery['stage'], + size, + routing_state, + state, + primary, + relocating_node, + size / 1024.0 + AS size_kb, + partition_ident FROM sys.shards - ORDER BY id, name; + WHERE routing_state = 'RELOCATING' + ORDER BY table_name, id, node['name']; """ - OPTIONS = ('all',) + OPTIONS = { + "state": STATE_STMT, + "relocating": RELOC_STMT, + } def complete(self, cmd, text): - return (i for i in self.OPTIONS if i.startswith(text) or text == '') + return (i for i in self.OPTIONS if i.startswith(text) or text.isspace()) def execute(self, cmd, stmt): success = cmd._exec(stmt) @@ -272,21 +310,28 @@ def execute(self, cmd, stmt): shards = cur.fetchall() if len(shards): cmd.pprint(shards, [c[0] for c in cur.description]) + else: + cmd.logger.info("No shards relocating!") return True def __call__(self, cmd, *args, **kwargs): - for stmt in (self.SHORT_STMT, self.RELOC_STMT if 'all' not in args else self.ALLSHARDS_STMT): + if len(args) == 0: + self.execute(cmd, self.DEFAULT_STMT) + return + + stmt = self.OPTIONS.get(args[0].strip()) + if stmt: self.execute(cmd, stmt) built_in_commands = { - '?': HelpCommand(), - 'r': ReadFileCommand(), - 'format': SwitchFormatCommand(), - 'autocomplete': ToggleAutocompleteCommand(), - 'autocapitalize': ToggleAutoCapitalizeCommand(), - 'verbose': ToggleVerboseCommand(), - 'check': CheckCommand(), - 'pager': SetPager(), - 'shards': ShardsCommand(), + "?": HelpCommand(), + "r": ReadFileCommand(), + "format": SwitchFormatCommand(), + "autocomplete": ToggleAutocompleteCommand(), + "autocapitalize": ToggleAutoCapitalizeCommand(), + "verbose": ToggleVerboseCommand(), + "check": CheckCommand(), + "pager": SetPager(), + "shards": ShardsCommand(), } From 880223e3554d189c7d867722d9749087d752b357 Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Sat, 21 Mar 2026 23:43:29 +0100 Subject: [PATCH 03/14] tests: add ShardsCommand tests --- tests/test_commands.py | 52 +++++++++++++++++++++++++++++++++++++++ tests/test_integration.py | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/tests/test_commands.py b/tests/test_commands.py index c778930d..c09e277d 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -35,6 +35,7 @@ ClusterCheckCommand, NodeCheckCommand, ReadFileCommand, + ShardsCommand, ToggleAutoCapitalizeCommand, ToggleAutocompleteCommand, ToggleVerboseCommand, @@ -267,6 +268,57 @@ def test_check_command_with_node_check(self, cmd): cmd.logger.info.assert_called_with('NODE CHECK OK') +class ShardsCommandTest(TestCase): + @patch('crate.crash.command.CrateShell') + def test_shards_command(self,cmd): + rows = [ + ["table1"," 6", "100000000","30065115537 ","1 "," 5038203304 "," 83.2423617404707 "], + ["table2"," 6", "100000000"," 8178261348 ","0 "," 0 "," 100.0 "], + ["table3"," 1000", " 0"," 208000 ","0 "," 0 "," 100.0 "], + ] + cols = [("table_name ", ), (" total_shards ", ), (" total_num_docs ", ), (" total_sum_shard_size ", ), (" relocating_shards ", ), (" relocating_size ", ), (" relocated_percent")] + cmd._exec.return_value = True + cmd.cursor.fetchall.return_value = rows + cmd.cursor.description = cols + + ShardsCommand()(cmd) + cmd.pprint.assert_called_with(rows, [c[0] for c in cols]) + + @patch('crate.crash.command.CrateShell') + def test_shards_command_state(self,cmd): + rows = [ + ["RELOCATING","2","33334465","9.307963063940406"], + ["STARTED","1010","166665535","26.309150873683393"], + ] + cols = [("routing_state", ), ("shard_count", ), ("num_docs", ), ("size_gb", )] + cmd._exec.return_value = True + cmd.cursor.fetchall.return_value = rows + cmd.cursor.description = cols + + ShardsCommand()(cmd,"state") + cmd.pprint.assert_called_with(rows, [c[0] for c in cols]) + + @patch('crate.crash.command.CrateShell') + def test_shards_command_relocating_none(self,cmd): + cmd._exec.return_value = True + cmd.cursor.fetchall.return_value = [] + ShardsCommand()(cmd,"relocating") + cmd.logger.info.assert_called_with('No shards relocating!') + + @patch('crate.crash.command.CrateShell') + def test_shards_command_relocating(self,cmd): + rows = [ + ["table1","node02","1","DONE","4997198327","RELOCATING","STARTED","TRUE","d63zx99jSfWjr5wUykEXWQ","4880076.4912109375",""], + ["table1","node01","2","DONE","4997150911","RELOCATING","STARTED","TRUE","d63zx99jSfWjr5wUykEXWQ","4880030.1865234375",""], + ] + cols = [("table_name",),("node['name']",),("id",),("recovery['stage']",),("size",),("routing_state",),("state",),("primary",),("relocating_node",),("size_kb",),("partition_ident",)] + cmd._exec.return_value = True + cmd.cursor.fetchall.return_value = rows + cmd.cursor.description = cols + + ShardsCommand()(cmd,"state") + cmd.pprint.assert_called_with(rows, [c[0] for c in cols]) + @patch('crate.client.connection.Cursor', fake_cursor()) class CommentsTest(TestCase): diff --git a/tests/test_integration.py b/tests/test_integration.py index e3d3734a..b0c3aaad 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -653,7 +653,7 @@ def test_help_command(self): '\\pager set an external pager. Use without argument to reset to internal paging', '\\q quit crash', '\\r read and execute statements from a file', - '\\shards short summary for shards and their relocation (use `all` as argument for longer output)', + '\\shards shows progress of shards relocation (optional arguments: `state` and `relocating`)', '\\sysinfo print system and cluster info', '\\verbose toggle verbose mode', ]) From 2786f0ad13b282bc4eb29dcc5f51f1b502bacc89 Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Sun, 22 Mar 2026 14:49:27 +0100 Subject: [PATCH 04/14] tests: add basic integration tests - empty views when db empty or no shards relocating --- src/crate/crash/commands.py | 8 +-- tests/test_commands.py | 22 +++---- tests/test_integration.py | 111 ++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 15 deletions(-) diff --git a/src/crate/crash/commands.py b/src/crate/crash/commands.py index 6a7a19b8..6141c9ec 100644 --- a/src/crate/crash/commands.py +++ b/src/crate/crash/commands.py @@ -308,10 +308,7 @@ def execute(self, cmd, stmt): cur = cmd.cursor shards = cur.fetchall() - if len(shards): - cmd.pprint(shards, [c[0] for c in cur.description]) - else: - cmd.logger.info("No shards relocating!") + cmd.pprint(shards, [c[0] for c in cur.description]) return True def __call__(self, cmd, *args, **kwargs): @@ -322,6 +319,9 @@ def __call__(self, cmd, *args, **kwargs): stmt = self.OPTIONS.get(args[0].strip()) if stmt: self.execute(cmd, stmt) + else: + cmd.logger.critical(f'Command argument not supported (available options: {", ".join(f"`{_a}`" for _a in self.OPTIONS.keys())}).') + return built_in_commands = { diff --git a/tests/test_commands.py b/tests/test_commands.py index c09e277d..fd10cd93 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -272,11 +272,11 @@ class ShardsCommandTest(TestCase): @patch('crate.crash.command.CrateShell') def test_shards_command(self,cmd): rows = [ - ["table1"," 6", "100000000","30065115537 ","1 "," 5038203304 "," 83.2423617404707 "], - ["table2"," 6", "100000000"," 8178261348 ","0 "," 0 "," 100.0 "], - ["table3"," 1000", " 0"," 208000 ","0 "," 0 "," 100.0 "], + ['table1','1','10','1024','0','0','100.0'], + ['table2','2','20','2048','1','1024','50.0'], + ['table3','3','30','3072','2','2048','33.3'], ] - cols = [("table_name ", ), (" total_shards ", ), (" total_num_docs ", ), (" total_sum_shard_size ", ), (" relocating_shards ", ), (" relocating_size ", ), (" relocated_percent")] + cols = [('table_name', ), (' total_shards', ), (' total_num_docs', ), (' total_sum_shard_size', ), (' relocating_shards', ), (' relocating_size', ), (' relocated_percent')] cmd._exec.return_value = True cmd.cursor.fetchall.return_value = rows cmd.cursor.description = cols @@ -287,10 +287,10 @@ def test_shards_command(self,cmd): @patch('crate.crash.command.CrateShell') def test_shards_command_state(self,cmd): rows = [ - ["RELOCATING","2","33334465","9.307963063940406"], - ["STARTED","1010","166665535","26.309150873683393"], + ['RELOCATING','2','33334465','9.307963063940406'], + ['STARTED','1010','166665535','26.309150873683393'], ] - cols = [("routing_state", ), ("shard_count", ), ("num_docs", ), ("size_gb", )] + cols = [('routing_state', ), ('shard_count', ), ('num_docs', ), ('size_gb', )] cmd._exec.return_value = True cmd.cursor.fetchall.return_value = rows cmd.cursor.description = cols @@ -303,15 +303,15 @@ def test_shards_command_relocating_none(self,cmd): cmd._exec.return_value = True cmd.cursor.fetchall.return_value = [] ShardsCommand()(cmd,"relocating") - cmd.logger.info.assert_called_with('No shards relocating!') + cmd.logger.info.assert_not_called() @patch('crate.crash.command.CrateShell') def test_shards_command_relocating(self,cmd): rows = [ - ["table1","node02","1","DONE","4997198327","RELOCATING","STARTED","TRUE","d63zx99jSfWjr5wUykEXWQ","4880076.4912109375",""], - ["table1","node01","2","DONE","4997150911","RELOCATING","STARTED","TRUE","d63zx99jSfWjr5wUykEXWQ","4880030.1865234375",""], + ['table1','node02','1','DONE','4997198327','RELOCATING','STARTED','TRUE','d63zx99jSfWjr5wUykEXWQ','4880076.4912109375',''], + ['table1','node01','2','DONE','4997150911','RELOCATING','STARTED','TRUE','d63zx99jSfWjr5wUykEXWQ','4880030.1865234375',''], ] - cols = [("table_name",),("node['name']",),("id",),("recovery['stage']",),("size",),("routing_state",),("state",),("primary",),("relocating_node",),("size_kb",),("partition_ident",)] + cols = [('table_name',),('node[\'name\']',),('id',),('recovery[\'stage\']',),('size',),('routing_state',),('state',),('primary',),('relocating_node',),('size_kb',),('partition_ident',)] cmd._exec.return_value = True cmd.cursor.fetchall.return_value = rows cmd.cursor.description = cols diff --git a/tests/test_integration.py b/tests/test_integration.py index b0c3aaad..38da7850 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -872,4 +872,115 @@ def test_connect_info_not_available(self, is_conn_available): self.assertEqual(crash.connect_info.schema, None) +class ShardsCommandEmptyDBTest(TestCase): + def setUp(self): + node.reset() + + def test_shards_command(self): + expected = '\n'.join([ + '+------------+--------------+----------------+----------------------+-------------------+-----------------+-------------------+', + '| table_name | total_shards | total_num_docs | total_sum_shard_size | relocating_shards | relocating_size | relocated_percent |', + '+------------+--------------+----------------+----------------------+-------------------+-----------------+-------------------+', + '+------------+--------------+----------------+----------------------+-------------------+-----------------+-------------------+\n', + ]) + with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: + shards_ = cmd.commands['shards'] + with patch('sys.stdout', new_callable=StringIO) as output: + text = shards_(cmd) + self.assertEqual(None, text) + self.assertEqual(expected, output.getvalue()) + + def test_shards_command_state(self): + expected = '\n'.join([ + '+---------------+-------------+----------+---------+', + '| routing_state | shard_count | num_docs | size_gb |', + '+---------------+-------------+----------+---------+', + '+---------------+-------------+----------+---------+\n', + ]) + with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: + shards_ = cmd.commands['shards'] + with patch('sys.stdout', new_callable=StringIO) as output: + text = shards_(cmd, 'state') + self.assertEqual(None, text) + self.assertEqual(expected, output.getvalue()) + + def test_shards_command_relocating(self): + expected = '\n'.join([ + '+------------+--------------+----+-------------------+------+---------------+-------+---------+-----------------+---------+-----------------+', + "| table_name | node['name'] | id | recovery['stage'] | size | routing_state | state | primary | relocating_node | size_kb | partition_ident |", + '+------------+--------------+----+-------------------+------+---------------+-------+---------+-----------------+---------+-----------------+', + '+------------+--------------+----+-------------------+------+---------------+-------+---------+-----------------+---------+-----------------+\n', + ]) + with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: + shards_ = cmd.commands['shards'] + with patch('sys.stdout', new_callable=StringIO) as output: + text = shards_(cmd, 'relocating') + self.assertEqual(None, text) + self.assertEqual(expected, output.getvalue()) + + def test_shards_command_wrong_argument(self): + with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: + shards_ = cmd.commands['shards'] + with patch('sys.stdout', new_callable=StringIO) as output: + cmd.logger = ColorPrinter(False, stream=output) + text = shards_(cmd, 'arg1', 'arg2') + self.assertEqual(None, text) + self.assertEqual('Command argument not supported (available options: `state`, `relocating`).\n', output.getvalue()) + + + +class ShardsCommandWithContentTest(TestCase): + def tearDown(self): + with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: + cmd.process('DROP TABLE IF EXISTS test_table;') + + def setUp(self): + node.reset() + with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: + cmd.process('CREATE TABLE test_table (id INTEGER PRIMARY KEY, data STRING ) CLUSTERED INTO 10 SHARDS WITH (number_of_replicas = 0);\n') + + def test_shards_command(self): + expected = '\n'.join([ + '+------------+--------------+----------------+----------------------+-------------------+-----------------+-------------------+', + '| table_name | total_shards | total_num_docs | total_sum_shard_size | relocating_shards | relocating_size | relocated_percent |', + '+------------+--------------+----------------+----------------------+-------------------+-----------------+-------------------+', + '| test_table | 10 | 0 | dummy | 0 | 0 | 100.0 |', + '+------------+--------------+----------------+----------------------+-------------------+-----------------+-------------------+\n', + ]) + with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: + shards_ = cmd.commands['shards'] + with patch('sys.stdout', new_callable=StringIO) as output: + text = shards_(cmd) + self.assertEqual(None, text) + self.assertEqual(len(expected.splitlines()), len(output.getvalue().splitlines())) + + def test_shards_command_state(self): + expected = '\n'.join([ + '+---------------+-------------+----------+-----------------------+', + '| routing_state | shard_count | num_docs | size_gb |', + '+---------------+-------------+----------+-----------------------+', + '| STARTED | 10 | 0 | 7.40AAAAAAAAAAAAAA-07 |', + '+---------------+-------------+----------+-----------------------+\n' + ]) + with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: + shards_ = cmd.commands['shards'] + with patch('sys.stdout', new_callable=StringIO) as output: + text = shards_(cmd, 'state') + self.assertEqual(None, text) + self.assertEqual(len(expected.splitlines()), len(output.getvalue().splitlines())) + + def test_shards_command_relocating(self): + expected = '\n'.join([ + '+------------+--------------+----+-------------------+------+---------------+-------+---------+-----------------+---------+-----------------+', + "| table_name | node['name'] | id | recovery['stage'] | size | routing_state | state | primary | relocating_node | size_kb | partition_ident |", + '+------------+--------------+----+-------------------+------+---------------+-------+---------+-----------------+---------+-----------------+', + '+------------+--------------+----+-------------------+------+---------------+-------+---------+-----------------+---------+-----------------+\n', + ]) + with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: + shards_ = cmd.commands['shards'] + with patch('sys.stdout', new_callable=StringIO) as output: + text = shards_(cmd, 'relocating') + self.assertEqual(None, text) + self.assertEqual(expected, output.getvalue()) + setup_logging(level=logging.INFO) From 022c19bb0225680c8bb561c4101395fe6aa8ddac Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Sun, 22 Mar 2026 15:14:00 +0100 Subject: [PATCH 05/14] chore: update CHANGES.txt with `\shards` feature --- CHANGES.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index eac4d698..ec6ac8a8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,9 @@ Changes for crash Unreleased ========== +- Added ``\shards`` to show shard relocation progress. With optinal arguments + ``state`` and ``relocating``. + 2026/02/09 0.32.0 ================= From e661b1fcd4c4ef64f2f92b6f8c7b4134470bc822 Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Sun, 22 Mar 2026 15:55:30 +0100 Subject: [PATCH 06/14] feat: \shards shows tables relocating first --- src/crate/crash/commands.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/crate/crash/commands.py b/src/crate/crash/commands.py index 6141c9ec..9229e299 100644 --- a/src/crate/crash/commands.py +++ b/src/crate/crash/commands.py @@ -238,24 +238,24 @@ class ShardsCommand(Command): """shows progress of shards relocation (optional arguments: `state` and `relocating`)""" DEFAULT_STMT = """ - SELECT - table_name, - COUNT(*) - AS total_shards, - SUM(num_docs) - AS total_num_docs, - SUM(size) - As total_sum_shard_size, - SUM(CASE WHEN routing_state = 'RELOCATING' THEN 1 ELSE 0 END) - AS relocating_shards, - SUM(CASE WHEN routing_state = 'RELOCATING' THEN size ELSE 0 END) - AS relocating_size, - 100.0 * SUM(CASE WHEN routing_state != 'RELOCATING' THEN size ELSE 0 END) / CAST(SUM(size) as DOUBLE) - AS relocated_percent - FROM sys.shards - WHERE routing_state != 'UNASSIGNED' - GROUP BY table_name - ORDER BY table_name; +SELECT +table_name, + COUNT(*) + AS total_shards, + SUM(num_docs) + AS total_num_docs, + SUM(size) + As total_sum_shard_size, + SUM(CASE WHEN routing_state = 'RELOCATING' THEN 1 ELSE 0 END) + AS relocating_shards, + SUM(CASE WHEN routing_state = 'RELOCATING' THEN size ELSE 0 END) + AS relocating_size, + 100.0 * SUM(CASE WHEN routing_state != 'RELOCATING' THEN size ELSE 0 END) / CAST(SUM(size) as DOUBLE) + AS relocated_percent +FROM sys.shards +WHERE routing_state != 'UNASSIGNED' +GROUP BY table_name +ORDER BY relocated_percent, table_name; """ STATE_STMT = """ From 292446804ee42eddf551682af2041e3cb400588a Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Mon, 6 Apr 2026 12:24:18 +0200 Subject: [PATCH 07/14] Apply suggestion from @seut Adds columns `schema_name` and `partition_ident` to the query and improves it by replacing `CASE WHEN` with `FILTER(WHERE ... )` Co-authored-by: Sebastian Utz --- src/crate/crash/commands.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/crate/crash/commands.py b/src/crate/crash/commands.py index 9229e299..7ad9a71d 100644 --- a/src/crate/crash/commands.py +++ b/src/crate/crash/commands.py @@ -239,23 +239,23 @@ class ShardsCommand(Command): DEFAULT_STMT = """ SELECT -table_name, + schema_name, + table_name, + partition_ident, COUNT(*) AS total_shards, - SUM(num_docs) - AS total_num_docs, SUM(size) - As total_sum_shard_size, - SUM(CASE WHEN routing_state = 'RELOCATING' THEN 1 ELSE 0 END) + As total_size, + COUNT(*) FILTER (WHERE routing_state = 'RELOCATING') AS relocating_shards, - SUM(CASE WHEN routing_state = 'RELOCATING' THEN size ELSE 0 END) + SUM(size) FILTER (WHERE routing_state = 'RELOCATING') AS relocating_size, - 100.0 * SUM(CASE WHEN routing_state != 'RELOCATING' THEN size ELSE 0 END) / CAST(SUM(size) as DOUBLE) + 100.0 * SUM(size) FILTER(WHERE routing_state != 'RELOCATING') / SUM(size) AS relocated_percent FROM sys.shards WHERE routing_state != 'UNASSIGNED' -GROUP BY table_name -ORDER BY relocated_percent, table_name; +GROUP BY schema_name, table_name, partition_ident +ORDER BY relocated_percent, schema_name, table_name, partition_ident; """ STATE_STMT = """ From bb2eed72881a85a51a054a99a3cb3a6d29417706 Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Mon, 6 Apr 2026 12:26:47 +0200 Subject: [PATCH 08/14] Apply suggestion from @seut Add `primary` column to the shards query Co-authored-by: Sebastian Utz --- src/crate/crash/commands.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/crate/crash/commands.py b/src/crate/crash/commands.py index 7ad9a71d..4a954611 100644 --- a/src/crate/crash/commands.py +++ b/src/crate/crash/commands.py @@ -260,16 +260,17 @@ class ShardsCommand(Command): STATE_STMT = """ SELECT - routing_state, + state, + primary, COUNT(*) AS shard_count, SUM(num_docs) AS num_docs, - SUM(size) / 1073741824.0 + SUM(size) / 1024^3 AS size_gb FROM sys.shards - GROUP BY routing_state - ORDER BY routing_state; + GROUP BY state, primary + ORDER BY state, primary; """ RELOC_STMT = """ From cbca86d5d5087c3de6db979e8e3313f67b9e981c Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Mon, 6 Apr 2026 12:44:01 +0200 Subject: [PATCH 09/14] Address suggestion to remove `shards relocating` cmd/query --- src/crate/crash/commands.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/crate/crash/commands.py b/src/crate/crash/commands.py index 4a954611..121e12b9 100644 --- a/src/crate/crash/commands.py +++ b/src/crate/crash/commands.py @@ -273,28 +273,9 @@ class ShardsCommand(Command): ORDER BY state, primary; """ - RELOC_STMT = """ - SELECT - table_name, - node['name'], - id, - recovery['stage'], - size, - routing_state, - state, - primary, - relocating_node, - size / 1024.0 - AS size_kb, - partition_ident - FROM sys.shards - WHERE routing_state = 'RELOCATING' - ORDER BY table_name, id, node['name']; - """ OPTIONS = { "state": STATE_STMT, - "relocating": RELOC_STMT, } def complete(self, cmd, text): From 4300264353bdffeaa5aa9cea77e448e7f79510c5 Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Mon, 6 Apr 2026 12:46:51 +0200 Subject: [PATCH 10/14] Address sugestion to make "\shards state" default - table shards view available via "\shards info" --- src/crate/crash/commands.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/crate/crash/commands.py b/src/crate/crash/commands.py index 121e12b9..df24455a 100644 --- a/src/crate/crash/commands.py +++ b/src/crate/crash/commands.py @@ -238,6 +238,21 @@ class ShardsCommand(Command): """shows progress of shards relocation (optional arguments: `state` and `relocating`)""" DEFAULT_STMT = """ + SELECT + state, + primary, + COUNT(*) + AS shard_count, + SUM(num_docs) + AS num_docs, + SUM(size) / 1024^3 + AS size_gb + FROM sys.shards + GROUP BY state, primary + ORDER BY state, primary; + """ + + INFO_STMT = """ SELECT schema_name, table_name, @@ -258,24 +273,9 @@ class ShardsCommand(Command): ORDER BY relocated_percent, schema_name, table_name, partition_ident; """ - STATE_STMT = """ - SELECT - state, - primary, - COUNT(*) - AS shard_count, - SUM(num_docs) - AS num_docs, - SUM(size) / 1024^3 - AS size_gb - FROM sys.shards - GROUP BY state, primary - ORDER BY state, primary; - """ - OPTIONS = { - "state": STATE_STMT, + "info": INFO_STMT, } def complete(self, cmd, text): From 09490b658d7f60305751b482a3f762b9628d9839 Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Mon, 6 Apr 2026 12:58:31 +0200 Subject: [PATCH 11/14] chore: adjust shards command description --- src/crate/crash/commands.py | 3 +-- tests/test_integration.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/crate/crash/commands.py b/src/crate/crash/commands.py index df24455a..31e43623 100644 --- a/src/crate/crash/commands.py +++ b/src/crate/crash/commands.py @@ -235,7 +235,7 @@ def __call__(self, cmd, check_name=None, **kwargs): class ShardsCommand(Command): - """shows progress of shards relocation (optional arguments: `state` and `relocating`)""" + """shows shards state, optionally per table, e.g. \\shards info""" DEFAULT_STMT = """ SELECT @@ -273,7 +273,6 @@ class ShardsCommand(Command): ORDER BY relocated_percent, schema_name, table_name, partition_ident; """ - OPTIONS = { "info": INFO_STMT, } diff --git a/tests/test_integration.py b/tests/test_integration.py index 38da7850..6f4344be 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -653,7 +653,7 @@ def test_help_command(self): '\\pager set an external pager. Use without argument to reset to internal paging', '\\q quit crash', '\\r read and execute statements from a file', - '\\shards shows progress of shards relocation (optional arguments: `state` and `relocating`)', + '\\shards shows shards state, optionally per table, e.g. \\shards info', '\\sysinfo print system and cluster info', '\\verbose toggle verbose mode', ]) From b5b80b61f9ed794a296a457d0d7a0591d9aa046c Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Mon, 6 Apr 2026 13:23:05 +0200 Subject: [PATCH 12/14] patch: fix issue with evaluating 1024 ^ 3 --- src/crate/crash/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crate/crash/commands.py b/src/crate/crash/commands.py index 31e43623..ff03733d 100644 --- a/src/crate/crash/commands.py +++ b/src/crate/crash/commands.py @@ -245,7 +245,7 @@ class ShardsCommand(Command): AS shard_count, SUM(num_docs) AS num_docs, - SUM(size) / 1024^3 + SUM(size) / 1073741824.0 AS size_gb FROM sys.shards GROUP BY state, primary From 210445e9100cd8297dcefc21c6e77ea26f3e1379 Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Mon, 6 Apr 2026 14:22:31 +0200 Subject: [PATCH 13/14] tests: fix and rename tests to match suggestions --- tests/test_commands.py | 39 +++++++-------- tests/test_integration.py | 100 +++++++++++++++----------------------- 2 files changed, 57 insertions(+), 82 deletions(-) diff --git a/tests/test_commands.py b/tests/test_commands.py index fd10cd93..af80f148 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -270,13 +270,12 @@ def test_check_command_with_node_check(self, cmd): class ShardsCommandTest(TestCase): @patch('crate.crash.command.CrateShell') - def test_shards_command(self,cmd): + def test_shards_command_default(self,cmd): rows = [ - ['table1','1','10','1024','0','0','100.0'], - ['table2','2','20','2048','1','1024','50.0'], - ['table3','3','30','3072','2','2048','33.3'], + ['RELOCATING','FALSE','2','33334465','9.307963063940406'], + ['STARTED','TRUE','1010','166665535','26.309150873683393'], ] - cols = [('table_name', ), (' total_shards', ), (' total_num_docs', ), (' total_sum_shard_size', ), (' relocating_shards', ), (' relocating_size', ), (' relocated_percent')] + cols = [('state', ), ('primary',), ('shard_count', ), ('num_docs', ), ('size_gb', )] cmd._exec.return_value = True cmd.cursor.fetchall.return_value = rows cmd.cursor.description = cols @@ -285,39 +284,35 @@ def test_shards_command(self,cmd): cmd.pprint.assert_called_with(rows, [c[0] for c in cols]) @patch('crate.crash.command.CrateShell') - def test_shards_command_state(self,cmd): + def test_shards_command_info(self,cmd): rows = [ - ['RELOCATING','2','33334465','9.307963063940406'], - ['STARTED','1010','166665535','26.309150873683393'], + ['doc','table1','','1','10','1024','0','100.0'], + ['doc','table2','','2','20','2048','1','50.0'], + ['doc','table3','','3','30','3072','2','33.3'], ] - cols = [('routing_state', ), ('shard_count', ), ('num_docs', ), ('size_gb', )] + cols = [('schema_name',),('table_name',),('partition_ident',),('total_shards',),('total_size',),('relocating_shards',),('relocating_size',),('relocated_percent',)] cmd._exec.return_value = True cmd.cursor.fetchall.return_value = rows cmd.cursor.description = cols - ShardsCommand()(cmd,"state") + ShardsCommand()(cmd, "info") cmd.pprint.assert_called_with(rows, [c[0] for c in cols]) + @patch('crate.crash.command.CrateShell') - def test_shards_command_relocating_none(self,cmd): + def test_shards_command_default_none(self,cmd): cmd._exec.return_value = True cmd.cursor.fetchall.return_value = [] - ShardsCommand()(cmd,"relocating") + ShardsCommand()(cmd) cmd.logger.info.assert_not_called() @patch('crate.crash.command.CrateShell') - def test_shards_command_relocating(self,cmd): - rows = [ - ['table1','node02','1','DONE','4997198327','RELOCATING','STARTED','TRUE','d63zx99jSfWjr5wUykEXWQ','4880076.4912109375',''], - ['table1','node01','2','DONE','4997150911','RELOCATING','STARTED','TRUE','d63zx99jSfWjr5wUykEXWQ','4880030.1865234375',''], - ] - cols = [('table_name',),('node[\'name\']',),('id',),('recovery[\'stage\']',),('size',),('routing_state',),('state',),('primary',),('relocating_node',),('size_kb',),('partition_ident',)] + def test_shards_command_info_none(self,cmd): cmd._exec.return_value = True - cmd.cursor.fetchall.return_value = rows - cmd.cursor.description = cols + cmd.cursor.fetchall.return_value = [] + ShardsCommand()(cmd,"info") + cmd.logger.info.assert_not_called() - ShardsCommand()(cmd,"state") - cmd.pprint.assert_called_with(rows, [c[0] for c in cols]) @patch('crate.client.connection.Cursor', fake_cursor()) class CommentsTest(TestCase): diff --git a/tests/test_integration.py b/tests/test_integration.py index 6f4344be..28741437 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -876,12 +876,12 @@ class ShardsCommandEmptyDBTest(TestCase): def setUp(self): node.reset() - def test_shards_command(self): + def test_shards_command_output_default(self): expected = '\n'.join([ - '+------------+--------------+----------------+----------------------+-------------------+-----------------+-------------------+', - '| table_name | total_shards | total_num_docs | total_sum_shard_size | relocating_shards | relocating_size | relocated_percent |', - '+------------+--------------+----------------+----------------------+-------------------+-----------------+-------------------+', - '+------------+--------------+----------------+----------------------+-------------------+-----------------+-------------------+\n', + '+-------+---------+-------------+----------+---------+', + '| state | primary | shard_count | num_docs | size_gb |', + '+-------+---------+-------------+----------+---------+', + '+-------+---------+-------------+----------+---------+\n', ]) with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: shards_ = cmd.commands['shards'] @@ -890,42 +890,29 @@ def test_shards_command(self): self.assertEqual(None, text) self.assertEqual(expected, output.getvalue()) - def test_shards_command_state(self): + def test_shards_command_output_info(self): expected = '\n'.join([ - '+---------------+-------------+----------+---------+', - '| routing_state | shard_count | num_docs | size_gb |', - '+---------------+-------------+----------+---------+', - '+---------------+-------------+----------+---------+\n', + '+-------------+------------+-----------------+--------------+------------+-------------------+-----------------+-------------------+', + '| schema_name | table_name | partition_ident | total_shards | total_size | relocating_shards | relocating_size | relocated_percent |', + '+-------------+------------+-----------------+--------------+------------+-------------------+-----------------+-------------------+', + '+-------------+------------+-----------------+--------------+------------+-------------------+-----------------+-------------------+\n', ]) with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: shards_ = cmd.commands['shards'] with patch('sys.stdout', new_callable=StringIO) as output: - text = shards_(cmd, 'state') + text = shards_(cmd, 'info') self.assertEqual(None, text) self.assertEqual(expected, output.getvalue()) - def test_shards_command_relocating(self): - expected = '\n'.join([ - '+------------+--------------+----+-------------------+------+---------------+-------+---------+-----------------+---------+-----------------+', - "| table_name | node['name'] | id | recovery['stage'] | size | routing_state | state | primary | relocating_node | size_kb | partition_ident |", - '+------------+--------------+----+-------------------+------+---------------+-------+---------+-----------------+---------+-----------------+', - '+------------+--------------+----+-------------------+------+---------------+-------+---------+-----------------+---------+-----------------+\n', - ]) - with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: - shards_ = cmd.commands['shards'] - with patch('sys.stdout', new_callable=StringIO) as output: - text = shards_(cmd, 'relocating') - self.assertEqual(None, text) - self.assertEqual(expected, output.getvalue()) - def test_shards_command_wrong_argument(self): + def test_shards_command_output_wrong_argument(self): with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: shards_ = cmd.commands['shards'] with patch('sys.stdout', new_callable=StringIO) as output: cmd.logger = ColorPrinter(False, stream=output) text = shards_(cmd, 'arg1', 'arg2') self.assertEqual(None, text) - self.assertEqual('Command argument not supported (available options: `state`, `relocating`).\n', output.getvalue()) + self.assertEqual('Command argument not supported (available options: `info`).\n', output.getvalue()) @@ -939,48 +926,41 @@ def setUp(self): with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: cmd.process('CREATE TABLE test_table (id INTEGER PRIMARY KEY, data STRING ) CLUSTERED INTO 10 SHARDS WITH (number_of_replicas = 0);\n') - def test_shards_command(self): - expected = '\n'.join([ - '+------------+--------------+----------------+----------------------+-------------------+-----------------+-------------------+', - '| table_name | total_shards | total_num_docs | total_sum_shard_size | relocating_shards | relocating_size | relocated_percent |', - '+------------+--------------+----------------+----------------------+-------------------+-----------------+-------------------+', - '| test_table | 10 | 0 | dummy | 0 | 0 | 100.0 |', - '+------------+--------------+----------------+----------------------+-------------------+-----------------+-------------------+\n', - ]) + def test_shards_command_output_default(self): + expected = [ + '+---------+---------+-------------+-----------+---------------------+', + '| state | primary | shard_count | num_docs | size_gb |', + '+---------+---------+-------------+-----------+---------------------+', + '| STARTED | FALSEy | ?6 | 1x0000000 | 7. DUMMY VALUE 614 |', + '+---------+---------+-------------+-----------+---------------------+\n', + ] with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: shards_ = cmd.commands['shards'] with patch('sys.stdout', new_callable=StringIO) as output: text = shards_(cmd) self.assertEqual(None, text) - self.assertEqual(len(expected.splitlines()), len(output.getvalue().splitlines())) - - def test_shards_command_state(self): - expected = '\n'.join([ - '+---------------+-------------+----------+-----------------------+', - '| routing_state | shard_count | num_docs | size_gb |', - '+---------------+-------------+----------+-----------------------+', - '| STARTED | 10 | 0 | 7.40AAAAAAAAAAAAAA-07 |', - '+---------------+-------------+----------+-----------------------+\n' - ]) - with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: - shards_ = cmd.commands['shards'] - with patch('sys.stdout', new_callable=StringIO) as output: - text = shards_(cmd, 'state') - self.assertEqual(None, text) - self.assertEqual(len(expected.splitlines()), len(output.getvalue().splitlines())) - - def test_shards_command_relocating(self): - expected = '\n'.join([ - '+------------+--------------+----+-------------------+------+---------------+-------+---------+-----------------+---------+-----------------+', - "| table_name | node['name'] | id | recovery['stage'] | size | routing_state | state | primary | relocating_node | size_kb | partition_ident |", - '+------------+--------------+----+-------------------+------+---------------+-------+---------+-----------------+---------+-----------------+', - '+------------+--------------+----+-------------------+------+---------------+-------+---------+-----------------+---------+-----------------+\n', - ]) + output_lines = output.getvalue().splitlines() + self.assertEqual(len(expected), len(output_lines)) + header = lambda x: [word.strip() for word in x[1].split('|')] + self.assertEqual(header(expected), header(output_lines)) + + + def test_shards_command_ouput_info(self): + expected = [ + '+-------------+------------+-----------------+--------------+------------+-------------------+-----------------+-------------------+', + '| schema_name | table_name | partition_ident | total_shards | total_size | relocating_shards | relocating_size | relocated_percent |', + '+-------------+------------+-----------------+--------------+------------+-------------------+-----------------+-------------------+', + '| doc | test_table | | 10 | 624 | 0 | NULL | 100.0 |', + '+-------------+------------+-----------------+--------------+------------+-------------------+-----------------+-------------------+\n', + ] with CrateShell(crate_hosts=[node.http_url], is_tty=False) as cmd: shards_ = cmd.commands['shards'] with patch('sys.stdout', new_callable=StringIO) as output: - text = shards_(cmd, 'relocating') + text = shards_(cmd, 'info') self.assertEqual(None, text) - self.assertEqual(expected, output.getvalue()) + output_lines = output.getvalue().splitlines() + self.assertEqual(len(expected), len(output_lines)) + header = lambda x: [word.strip() for word in x[1].split('|')] + self.assertEqual(header(expected), header(output_lines)) setup_logging(level=logging.INFO) From e0976811c7d26d305307d99f7a11685ed62da93e Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Mon, 6 Apr 2026 15:12:26 +0200 Subject: [PATCH 14/14] misc: formating --- src/crate/crash/commands.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/crate/crash/commands.py b/src/crate/crash/commands.py index ff03733d..c34fbfbf 100644 --- a/src/crate/crash/commands.py +++ b/src/crate/crash/commands.py @@ -253,24 +253,24 @@ class ShardsCommand(Command): """ INFO_STMT = """ -SELECT - schema_name, - table_name, - partition_ident, - COUNT(*) - AS total_shards, - SUM(size) - As total_size, - COUNT(*) FILTER (WHERE routing_state = 'RELOCATING') - AS relocating_shards, - SUM(size) FILTER (WHERE routing_state = 'RELOCATING') - AS relocating_size, - 100.0 * SUM(size) FILTER(WHERE routing_state != 'RELOCATING') / SUM(size) - AS relocated_percent -FROM sys.shards -WHERE routing_state != 'UNASSIGNED' -GROUP BY schema_name, table_name, partition_ident -ORDER BY relocated_percent, schema_name, table_name, partition_ident; + SELECT + schema_name, + table_name, + partition_ident, + COUNT(*) + AS total_shards, + SUM(size) + As total_size, + COUNT(*) FILTER (WHERE routing_state = 'RELOCATING') + AS relocating_shards, + SUM(size) FILTER (WHERE routing_state = 'RELOCATING') + AS relocating_size, + 100.0 * SUM(size) FILTER(WHERE routing_state != 'RELOCATING') / SUM(size) + AS relocated_percent + FROM sys.shards + WHERE routing_state != 'UNASSIGNED' + GROUP BY schema_name, table_name, partition_ident + ORDER BY relocated_percent, schema_name, table_name, partition_ident; """ OPTIONS = { @@ -302,7 +302,6 @@ def __call__(self, cmd, *args, **kwargs): self.execute(cmd, stmt) else: cmd.logger.critical(f'Command argument not supported (available options: {", ".join(f"`{_a}`" for _a in self.OPTIONS.keys())}).') - return built_in_commands = {