Skip to content

Commit 2b0b9dc

Browse files
authored
feat: Add list-tables command to show tables for a given namespace (#150)
1 parent 1f9e390 commit 2b0b9dc

6 files changed

Lines changed: 99 additions & 4 deletions

File tree

examples/scratch/.ice-rest-catalog.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ uri: jdbc:sqlite:file:data/ice-rest-catalog/db.sqlite?journal_mode=WAL&synchrono
22

33
# To use etcd instead of sqlite, start etcd with `etcd --data-dir=data/etcd`, then uncomment the line below
44
#uri: etcd:http://localhost:2379
5-
5+
#uri: etcd:http://127.0.0.1:12379,http://127.0.0.1:12479,http://127.0.0.1:12579
66
warehouse: s3://bucket1
77

88
s3:

ice-rest-catalog/src/test/resources/scenarios/basic-operations/run.sh.tmpl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,17 @@ fi
123123
{{ICE_CLI}} --config {{CLI_CONFIG}} files ${TABLE_SORTED} >> /tmp/basic_ops_files.txt
124124
echo "OK Listed files in tables"
125125

126+
# List tables in the namespace via list-tables command
127+
{{ICE_CLI}} --config {{CLI_CONFIG}} list-tables ${NAMESPACE_NAME} > /tmp/basic_ops_list_tables.txt
128+
for t in ${TABLE_IRIS} ${TABLE_PARTITIONED} ${TABLE_SORTED}; do
129+
if ! grep -q "${t}" /tmp/basic_ops_list_tables.txt; then
130+
echo "FAIL: list-tables output missing table ${t}"
131+
cat /tmp/basic_ops_list_tables.txt
132+
exit 1
133+
fi
134+
done
135+
echo "OK list-tables listed tables in ${NAMESPACE_NAME}"
136+
126137
# Cleanup tables then namespace
127138
{{ICE_CLI}} --config {{CLI_CONFIG}} delete-table ${TABLE_IRIS}
128139
{{ICE_CLI}} --config {{CLI_CONFIG}} delete-table ${TABLE_PARTITIONED}

ice-rest-catalog/src/test/resources/scenarios/basic-operations/verify.sh.tmpl

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ set -e
55
# Exit code 0 = success, non-zero = failure
66
# Expects run.sh to have written: basic_ops_list_namespace.txt, basic_ops_describe.txt,
77
# basic_ops_scan_iris.txt, basic_ops_scan_partitioned.txt, basic_ops_scan_sorted.txt,
8-
# basic_ops_files.txt under /tmp
8+
# basic_ops_files.txt, basic_ops_list_tables.txt under /tmp
99

1010
echo "Verifying basic operations test..."
1111

12-
for f in /tmp/basic_ops_list_namespace.txt /tmp/basic_ops_describe.txt /tmp/basic_ops_scan_iris.txt /tmp/basic_ops_scan_partitioned.txt /tmp/basic_ops_scan_sorted.txt; do
12+
for f in /tmp/basic_ops_list_namespace.txt /tmp/basic_ops_describe.txt /tmp/basic_ops_scan_iris.txt /tmp/basic_ops_scan_partitioned.txt /tmp/basic_ops_scan_sorted.txt /tmp/basic_ops_list_tables.txt; do
1313
if [ ! -f "$f" ]; then
1414
echo "FAIL Output file not found: $f"
1515
exit 1
@@ -72,8 +72,16 @@ if ! grep -q "test_ns" /tmp/basic_ops_list_namespace.txt; then
7272
exit 1
7373
fi
7474

75+
# Verify list-tables output contains the expected tables in the test namespace
76+
for t in test_ns.iris test_ns.taxis_p_by_day test_ns.taxis_s_by_day; do
77+
if ! grep -q "$t" /tmp/basic_ops_list_tables.txt; then
78+
echo "FAIL basic_ops_list_tables.txt does not contain expected table '$t'"
79+
exit 1
80+
fi
81+
done
82+
7583
# Cleanup temp files
76-
rm -f /tmp/basic_ops_list_namespace.txt /tmp/basic_ops_describe.txt /tmp/basic_ops_scan_iris.txt /tmp/basic_ops_scan_partitioned.txt /tmp/basic_ops_scan_sorted.txt /tmp/basic_ops_files.txt
84+
rm -f /tmp/basic_ops_list_namespace.txt /tmp/basic_ops_describe.txt /tmp/basic_ops_scan_iris.txt /tmp/basic_ops_scan_partitioned.txt /tmp/basic_ops_scan_sorted.txt /tmp/basic_ops_files.txt /tmp/basic_ops_list_tables.txt
7785

7886
echo "OK Verification passed"
7987
exit 0

ice/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ where `filelist` contains one file path per line. If any file fails, the entire
140140
```shell
141141
ice create-namespace flowers
142142
ice list-namespaces
143+
ice list-tables flowers
143144
ice delete-namespace flowers
144145
```
145146

ice/src/main/java/com/altinity/ice/cli/Main.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.altinity.ice.cli.internal.cmd.InsertWatch;
2525
import com.altinity.ice.cli.internal.cmd.ListNamespaces;
2626
import com.altinity.ice.cli.internal.cmd.ListPartitions;
27+
import com.altinity.ice.cli.internal.cmd.ListTables;
2728
import com.altinity.ice.cli.internal.cmd.Scan;
2829
import com.altinity.ice.cli.internal.config.Config;
2930
import com.altinity.ice.cli.internal.iceberg.rest.RESTCatalogFactory;
@@ -752,6 +753,33 @@ void listNamespaces(
752753
}
753754
}
754755

756+
@CommandLine.Command(name = "list-tables", description = "List tables in a namespace.")
757+
void listTables(
758+
@CommandLine.Parameters(
759+
arity = "1",
760+
paramLabel = "<namespace>",
761+
description = "Namespace to list tables from (e.g. parent_ns.child_ns)")
762+
String namespaceName,
763+
@CommandLine.Option(
764+
names = {"--json"},
765+
description = "Output JSON instead of YAML")
766+
boolean json)
767+
throws IOException {
768+
if (namespaceName == null || namespaceName.isEmpty()) {
769+
throw new IllegalArgumentException("Namespace name is required");
770+
}
771+
String[] split = namespaceName.split("[.]");
772+
for (String level : split) {
773+
if (level.isEmpty()) {
774+
throw new IllegalArgumentException(
775+
"Invalid namespace name: '.' cannot separate empty names");
776+
}
777+
}
778+
try (RESTCatalog catalog = loadCatalog()) {
779+
ListTables.run(catalog, Namespace.of(split), json);
780+
}
781+
}
782+
755783
@CommandLine.Command(name = "delete", description = "Delete data from catalog.")
756784
void delete(
757785
@CommandLine.Parameters(
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2025 Altinity Inc and/or its affiliates. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*/
10+
package com.altinity.ice.cli.internal.cmd;
11+
12+
import com.fasterxml.jackson.annotation.JsonInclude;
13+
import com.fasterxml.jackson.databind.ObjectMapper;
14+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
15+
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
16+
import java.io.IOException;
17+
import java.util.List;
18+
import java.util.stream.Collectors;
19+
import org.apache.iceberg.catalog.Namespace;
20+
import org.apache.iceberg.catalog.TableIdentifier;
21+
import org.apache.iceberg.rest.RESTCatalog;
22+
23+
public final class ListTables {
24+
25+
private ListTables() {}
26+
27+
public static void run(RESTCatalog catalog, Namespace namespace, boolean json)
28+
throws IOException {
29+
List<TableIdentifier> tables = catalog.listTables(namespace);
30+
List<String> names =
31+
tables.stream().map(TableIdentifier::toString).sorted().collect(Collectors.toList());
32+
var result = new Result(namespace.toString(), names);
33+
output(result, json);
34+
}
35+
36+
private static void output(Result result, boolean json) throws IOException {
37+
ObjectMapper mapper =
38+
json
39+
? new ObjectMapper()
40+
: new ObjectMapper(new YAMLFactory().enable(YAMLGenerator.Feature.MINIMIZE_QUOTES));
41+
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
42+
System.out.println(mapper.writeValueAsString(result));
43+
}
44+
45+
@JsonInclude(JsonInclude.Include.NON_NULL)
46+
record Result(String namespace, List<String> tables) {}
47+
}

0 commit comments

Comments
 (0)