You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by ze...@apache.org on 2026/08/07 18:07:34 UTC

(iceberg-go) branch main updated: fix(cli): accept case-insentive catalog types in cli (#1650)

This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 80b8ab5a2 fix(cli): accept case-insentive catalog types in cli (#1650)
80b8ab5a2 is described below

commit 80b8ab5a26ebe22ffa60143ad64de3869526a07c
Author: jj.lee <63...@users.noreply.github.com>
AuthorDate: Sat Aug 8 03:07:29 2026 +0900

    fix(cli): accept case-insentive catalog types in cli (#1650)
    
    * ToLower Catalog args in cli
    
    * consistent ToLower in registry
    
    * added mixed-case for test
---
 catalog/registry.go      |   2 +-
 catalog/registry_test.go |  19 ++++++
 cmd/iceberg/args_test.go | 146 +++++++++++++++++++++++++++++++++++++++++++++++
 cmd/iceberg/main.go      |   2 +-
 4 files changed, 167 insertions(+), 2 deletions(-)

diff --git a/catalog/registry.go b/catalog/registry.go
index c43f6e275..f1bd0c8af 100644
--- a/catalog/registry.go
+++ b/catalog/registry.go
@@ -158,7 +158,7 @@ func Load(ctx context.Context, name string, props iceberg.Properties) (Catalog,
 		}
 	}
 
-	cat, ok := defaultRegistry.get(catalogType)
+	cat, ok := defaultRegistry.get(strings.ToLower(catalogType))
 	if !ok {
 		return nil, fmt.Errorf("%w: %s", ErrCatalogNotFound, catalogType)
 	}
diff --git a/catalog/registry_test.go b/catalog/registry_test.go
index bde8d60b5..3c5007b64 100644
--- a/catalog/registry_test.go
+++ b/catalog/registry_test.go
@@ -33,6 +33,25 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+func TestCatalogLoadTypeIsCaseInsensitive(t *testing.T) {
+	ctx := context.Background()
+	defer catalog.Unregister("foobar")
+
+	catalog.Register("foobar", catalog.RegistrarFunc(func(ctx context.Context, s string, p iceberg.Properties) (catalog.Catalog, error) {
+		return nil, nil
+	}))
+
+	for _, catalogType := range []string{"foobar", "FOOBAR", "FooBar"} {
+		c, err := catalog.Load(ctx, "not found", iceberg.Properties{"type": catalogType})
+		assert.Nil(t, c)
+		assert.NoError(t, err)
+	}
+
+	c, err := catalog.Load(ctx, "not found", iceberg.Properties{"type": "FOOBARX"})
+	assert.Nil(t, c)
+	assert.ErrorIs(t, err, catalog.ErrCatalogNotFound)
+}
+
 func TestCatalogRegistry(t *testing.T) {
 	ctx := context.Background()
 	assert.ElementsMatch(t, []string{
diff --git a/cmd/iceberg/args_test.go b/cmd/iceberg/args_test.go
index 9329b1b95..9ec250d76 100644
--- a/cmd/iceberg/args_test.go
+++ b/cmd/iceberg/args_test.go
@@ -18,6 +18,9 @@
 package main
 
 import (
+	"net"
+	"net/http"
+	"net/http/httptest"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -382,6 +385,149 @@ func TestCLIExplicitMissingConfigFailsBeforeCatalogInit(t *testing.T) {
 	assert.Contains(t, string(out), path)
 }
 
+func TestCLIAcceptsMixedCaseCatalogType(t *testing.T) {
+	if testing.Short() {
+		t.Skip("spawns a subprocess")
+	}
+
+	restMux := http.NewServeMux()
+	restMux.HandleFunc("/v1/config", func(w http.ResponseWriter, req *http.Request) {
+		require.Equal(t, http.MethodGet, req.Method)
+		w.Header().Set("Content-Type", "application/json")
+		_, _ = w.Write([]byte(`{"defaults":{},"overrides":{},"endpoints":["GET /v1/{prefix}/namespaces"]}`))
+	})
+	restMux.HandleFunc("/v1/namespaces", func(w http.ResponseWriter, req *http.Request) {
+		require.Equal(t, http.MethodGet, req.Method)
+		w.Header().Set("Content-Type", "application/json")
+		_, _ = w.Write([]byte(`{"namespaces":[]}`))
+	})
+	restSrv := httptest.NewServer(restMux)
+	defer restSrv.Close()
+
+	glueMux := http.NewServeMux()
+	glueMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+		w.Header().Set("Content-Type", "application/x-amz-json-1.1")
+		_, _ = w.Write([]byte(`{"DatabaseList":[]}`))
+	})
+	glueSrv := httptest.NewServer(glueMux)
+	defer glueSrv.Close()
+
+	hadoopWarehouse := t.TempDir()
+
+	// A raw TCP listener stands in for a Hive metastore: the CLI only needs to
+	// get far enough to open a connection to prove "HIVE"/"Hive" were recognized
+	// as the Hive catalog type. It has no Thrift handshake, so the command still
+	// fails, but with an EOF/protocol error rather than "unrecognized catalog type".
+	hiveLn, err := net.Listen("tcp", "127.0.0.1:0")
+	require.NoError(t, err)
+	defer hiveLn.Close()
+	go func() {
+		for {
+			conn, err := hiveLn.Accept()
+			if err != nil {
+				return
+			}
+			conn.Close()
+		}
+	}()
+	hiveURI := "thrift://" + hiveLn.Addr().String()
+
+	tests := []struct {
+		name string
+		args []string
+		env  []string
+		// wantErr is set for cases where the catalog type is recognized but the
+		// backend call itself is expected to fail (e.g. no real Hive metastore
+		// behind the raw TCP listener).
+		wantErr bool
+		// wantUnrecognized is set only for catalog type values that must still be
+		// rejected outright, to keep that failure mode distinguishable from
+		// wantErr above.
+		wantUnrecognized bool
+	}{
+		{
+			name: "rest lowercase",
+			args: []string{"list", "--catalog", "rest", "--uri", restSrv.URL},
+		},
+		{
+			name: "rest uppercase",
+			args: []string{"list", "--catalog", "REST", "--uri", restSrv.URL},
+		},
+		{
+			name: "rest mixed case",
+			args: []string{"list", "--catalog", "Rest", "--uri", restSrv.URL},
+		},
+		{
+			name: "glue uppercase",
+			args: []string{"list", "--catalog", "GLUE"},
+			env: []string{
+				"AWS_ENDPOINT_URL=" + glueSrv.URL,
+				"AWS_ACCESS_KEY_ID=test",
+				"AWS_SECRET_ACCESS_KEY=test",
+				"AWS_REGION=us-east-1",
+			},
+		},
+		{
+			name: "glue mixed case",
+			args: []string{"list", "--catalog", "Glue"},
+			env: []string{
+				"AWS_ENDPOINT_URL=" + glueSrv.URL,
+				"AWS_ACCESS_KEY_ID=test",
+				"AWS_SECRET_ACCESS_KEY=test",
+				"AWS_REGION=us-east-1",
+			},
+		},
+		{
+			name: "hadoop uppercase",
+			args: []string{"list", "--catalog", "HADOOP", "--warehouse", hadoopWarehouse},
+		},
+		{
+			name: "hadoop mixed case",
+			args: []string{"list", "--catalog", "Hadoop", "--warehouse", hadoopWarehouse},
+		},
+		{
+			name:    "hive uppercase",
+			args:    []string{"list", "--catalog", "HIVE", "--uri", hiveURI},
+			wantErr: true,
+		},
+		{
+			name:    "hive mixed case",
+			args:    []string{"list", "--catalog", "Hive", "--uri", hiveURI},
+			wantErr: true,
+		},
+		{
+			name:             "unknown catalog type is still rejected",
+			args:             []string{"list", "--catalog", "RESTX", "--uri", restSrv.URL},
+			wantUnrecognized: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			cmd := exec.Command(os.Args[0], tt.args...)
+			cmd.Env = append(os.Environ(), icebergCLISubprocessEnv+"=1")
+			cmd.Env = append(cmd.Env, tt.env...)
+			out, err := cmd.CombinedOutput()
+
+			if tt.wantUnrecognized {
+				require.Error(t, err, "output: %s", out)
+				require.Contains(t, string(out), "unrecognized catalog type")
+
+				return
+			}
+
+			if tt.wantErr {
+				require.Error(t, err, "output: %s", out)
+				require.NotContains(t, string(out), "unrecognized catalog type")
+
+				return
+			}
+
+			require.NoError(t, err, "output: %s", out)
+		})
+	}
+}
+
 func TestMergeConfAwsProfile(t *testing.T) {
 	fileCfg := &config.CatalogConfig{AwsProfile: "file-profile"}
 
diff --git a/cmd/iceberg/main.go b/cmd/iceberg/main.go
index fe865af05..189a7f662 100644
--- a/cmd/iceberg/main.go
+++ b/cmd/iceberg/main.go
@@ -358,7 +358,7 @@ func initCatalog(ctx context.Context, args Args) catalog.Catalog {
 		err error
 	)
 
-	switch catalog.Type(args.Catalog) {
+	switch catalog.Type(strings.ToLower(args.Catalog)) {
 	case catalog.REST:
 		opts := []rest.Option{}
 		if len(args.Token) > 0 {