This is an automated email from the ASF dual-hosted git repository.
laskoviymishka 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 ec863888d refactor(encryption)!: replace global KMS registration with
isolated registries (#2296)
ec863888d is described below
commit ec863888dccf543972e7bbfbf6b3fbbe8522a4db
Author: Minh Vu <[email protected]>
AuthorDate: Tue Oct 27 15:40:22 2026 +0100
refactor(encryption)!: replace global KMS registration with isolated
registries (#2296)
---
encryption/kms_registry.go | 198 +++++++++++++++++++++--------
encryption/kms_registry_test.go | 267 ++++++++++++++++++++++++++++++++++------
website/src/releases.md | 1 +
3 files changed, 376 insertions(+), 90 deletions(-)
diff --git a/encryption/kms_registry.go b/encryption/kms_registry.go
index 6bbc29f62..188871e50 100644
--- a/encryption/kms_registry.go
+++ b/encryption/kms_registry.go
@@ -18,6 +18,7 @@
package encryption
import (
+ "context"
"errors"
"fmt"
"maps"
@@ -26,86 +27,179 @@ import (
)
// KMSTypeKey is the catalog property that selects a [KeyManagementClient]
-// implementation registered via [RegisterKMS] (e.g. "memory"). It is a
-// registered short name, not a fully-qualified class name.
-//
-// KMSTypeKey ("kms-type") is an iceberg-go-specific mechanism for wiring up
-// a KMS client at runtime. It is not a standardized Iceberg spec property
-// and has no cross-implementation meaning. It will not be recognized by,
-// or portable to, other Iceberg implementations.
+// implementation registered with a [KMSRegistry] (e.g. "memory").
+// It is an iceberg-go-specific property and is not portable to other Iceberg
+// implementations.
const KMSTypeKey = "kms-type"
-// ErrKMSTypeNotFound is returned by [LoadKeyManagementClient] when the
-// requested kmsType has not been registered via [RegisterKMS].
-var ErrKMSTypeNotFound = errors.New("encryption: kms type not found")
+var (
+ // ErrKMSTypeNotFound is returned by [KMSRegistry.Load] when the
+ // requested KMS has not been registered.
+ ErrKMSTypeNotFound = errors.New("encryption: kms type not found")
-// KMSFactory creates a [KeyManagementClient] from catalog/table properties.
-// props is the full property map (typically catalog properties); factories
-// should read whichever keys they need (e.g. key material, endpoints,
-// credentials) directly from it.
-type KMSFactory func(props map[string]string) (KeyManagementClient, error)
+ // ErrKMSAlreadyRegistered is returned when a factory name is already
in use.
+ ErrKMSAlreadyRegistered = errors.New("encryption: kms factory already
registered")
-var (
- kmsRegMutex sync.RWMutex
- kmsRegistry = map[string]KMSFactory{}
+ // ErrInvalidKMSRegistration is returned for an empty name or nil
factory.
+ ErrInvalidKMSRegistration = errors.New("encryption: invalid kms
registration")
)
-// RegisterKMS adds a new named [KMSFactory] to the registry so it can later
-// be selected via the [KMSTypeKey] catalog property. It panics if factory is
-// nil or if name is already registered.
-func RegisterKMS(name string, factory KMSFactory) {
+// KMSFactory creates a [KeyManagementClient] from catalog/table properties.
+// Factories receive the caller's context and full property map.
+type KMSFactory func(ctx context.Context, props map[string]string)
(KeyManagementClient, error)
+
+// KMSRegistry owns the named factories used to construct KMS clients.
+// A registry is safe for concurrent use and must not be copied after first
use.
+// Its zero value is an empty, usable registry. Use [NewKMSRegistry] to clone
+// the global default, which includes the built-in "memory" factory (for
testing).
+//
+// Separate registries let tests and catalog clients register the same name
+// without modifying each other's factories.
+type KMSRegistry struct {
+ mu sync.RWMutex
+ factories map[string]KMSFactory
+}
+
+func builtInKMSRegistry() *KMSRegistry {
+ return &KMSRegistry{
+ factories: map[string]KMSFactory{
+ "memory": func(_ context.Context, _ map[string]string)
(KeyManagementClient, error) {
+ return NewInMemoryKeyManagementClient(), nil
+ },
+ },
+ }
+}
+
+// defaultKMSRegistry receives package-level registrations, including those
+// made by cloud KMS providers through side-effect imports.
+var defaultKMSRegistry = builtInKMSRegistry()
+
+// NewKMSRegistry returns an independent snapshot of the default registry.
+// Providers should be imported before calling it. Initializing a package-level
+// variable with NewKMSRegistry can miss registrations made by other packages.
+func NewKMSRegistry() *KMSRegistry {
+ return defaultKMSRegistry.Clone()
+}
+
+// Clone copies the registered factories into a separate registry. Factories
+// themselves are shared, but future registrations and removals are isolated.
+func (r *KMSRegistry) Clone() *KMSRegistry {
+ r.mu.RLock()
+ defer r.mu.RUnlock()
+
+ return &KMSRegistry{factories: maps.Clone(r.factories)}
+}
+
+// Register adds a named KMS factory. It returns an error for an empty name,
+// nil factory, or duplicate name. The registry is unchanged on error.
+func (r *KMSRegistry) Register(name string, factory KMSFactory) error {
+ if name == "" {
+ return fmt.Errorf("%w: name must not be empty",
ErrInvalidKMSRegistration)
+ }
if factory == nil {
- panic("encryption: RegisterKMS factory is nil")
+ return fmt.Errorf("%w: factory for %q is nil",
ErrInvalidKMSRegistration, name)
}
- kmsRegMutex.Lock()
- defer kmsRegMutex.Unlock()
+ r.mu.Lock()
+ defer r.mu.Unlock()
- if _, dup := kmsRegistry[name]; dup {
- panic("encryption: RegisterKMS called twice for name " + name)
+ if _, exists := r.factories[name]; exists {
+ return fmt.Errorf("%w: %q", ErrKMSAlreadyRegistered, name)
+ }
+ if r.factories == nil {
+ r.factories = make(map[string]KMSFactory)
}
- kmsRegistry[name] = factory
+ r.factories[name] = factory
+
+ return nil
}
-// UnregisterKMS removes the requested named factory from the registry.
-func UnregisterKMS(name string) {
- kmsRegMutex.Lock()
- defer kmsRegMutex.Unlock()
- delete(kmsRegistry, name)
+// MustRegister registers a factory or panics. It is intended for init
functions
+// in KMS provider packages where registration failure is unrecoverable.
+func (r *KMSRegistry) MustRegister(name string, factory KMSFactory) {
+ if err := r.Register(name, factory); err != nil {
+ panic(err)
+ }
}
-// GetRegisteredKMSNames returns the names of all currently registered KMS
-// factories.
-func GetRegisteredKMSNames() []string {
- kmsRegMutex.RLock()
- defer kmsRegMutex.RUnlock()
+// Unregister removes a named factory. Removing an unknown name is a no-op.
+func (r *KMSRegistry) Unregister(name string) {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ delete(r.factories, name)
+}
+
+// Names returns the currently registered factory names in sorted order.
+// The returned slice is independent of the registry.
+func (r *KMSRegistry) Names() []string {
+ r.mu.RLock()
+ defer r.mu.RUnlock()
- return slices.Collect(maps.Keys(kmsRegistry))
+ names := make([]string, 0, len(r.factories))
+ for name := range r.factories {
+ names = append(names, name)
+ }
+ slices.Sort(names)
+
+ return names
}
-// LoadKeyManagementClient resolves and constructs a [KeyManagementClient]
-// from props. It looks up the KMS name under [KMSTypeKey]; it returns an
-// error wrapping [ErrKMSTypeNotFound] if the property is unset or the named
-// KMS has not been registered via [RegisterKMS].
-func LoadKeyManagementClient(props map[string]string) (KeyManagementClient,
error) {
+// Load resolves and constructs a [KeyManagementClient] from props. It looks
+// up the name under [KMSTypeKey] and returns an error wrapping
+// [ErrKMSTypeNotFound] when the property is empty or not registered.
+// The factory is invoked outside the lock, so it may update this registry.
+func (r *KMSRegistry) Load(ctx context.Context, props map[string]string)
(KeyManagementClient, error) {
name := props[KMSTypeKey]
if name == "" {
return nil, fmt.Errorf("%w: %q is not set", ErrKMSTypeNotFound,
KMSTypeKey)
}
- kmsRegMutex.RLock()
- factory, ok := kmsRegistry[name]
- kmsRegMutex.RUnlock()
+ r.mu.RLock()
+ factory, ok := r.factories[name]
+ r.mu.RUnlock()
if !ok {
return nil, fmt.Errorf("%w: %q", ErrKMSTypeNotFound, name)
}
- return factory(props)
+ return factory(ctx, props)
+}
+
+// RegisterKMS adds a factory to the process-wide default registry. As in
+// previous releases, invalid or duplicate registrations panic. Use a separate
+// KMSRegistry and its Register method to handle registration errors.
+func RegisterKMS(name string, factory KMSFactory) {
+ if factory == nil {
+ panic("encryption: RegisterKMS factory is nil")
+ }
+ if err := defaultKMSRegistry.Register(name, factory); err != nil {
+ if errors.Is(err, ErrKMSAlreadyRegistered) {
+ panic("encryption: RegisterKMS called twice for name "
+ name)
+ }
+ panic(err)
+ }
+}
+
+// MustRegisterKMS registers a global KMS factory or panics. It is intended
+// for use by KMS providers during package initialization.
+func MustRegisterKMS(name string, factory KMSFactory) {
+ RegisterKMS(name, factory)
+}
+
+// UnregisterKMS removes a factory from the default registry. Existing
+// independent registry snapshots are unaffected.
+func UnregisterKMS(name string) {
+ defaultKMSRegistry.Unregister(name)
+}
+
+// GetRegisteredKMSNames returns globally registered factory names, sorted.
+func GetRegisteredKMSNames() []string {
+ return defaultKMSRegistry.Names()
}
-func init() {
- RegisterKMS("memory", func(_ map[string]string) (KeyManagementClient,
error) {
- return NewInMemoryKeyManagementClient(), nil
- })
+// LoadKeyManagementClient constructs a KMS client using the default registry.
+// Its context is forwarded to the selected factory.
+func LoadKeyManagementClient(ctx context.Context, props map[string]string)
(KeyManagementClient, error) {
+ return defaultKMSRegistry.Load(ctx, props)
}
diff --git a/encryption/kms_registry_test.go b/encryption/kms_registry_test.go
index 083d68717..358287d00 100644
--- a/encryption/kms_registry_test.go
+++ b/encryption/kms_registry_test.go
@@ -18,6 +18,9 @@
package encryption_test
import (
+ "context"
+ "fmt"
+ "sync"
"testing"
"github.com/apache/iceberg-go/encryption"
@@ -25,81 +28,269 @@ import (
"github.com/stretchr/testify/require"
)
-func TestLoadKeyManagementClient_Memory(t *testing.T) {
- kms, err := encryption.LoadKeyManagementClient(map[string]string{
+func memoryKMSFactory(_ context.Context, _ map[string]string)
(encryption.KeyManagementClient, error) {
+ return encryption.NewInMemoryKeyManagementClient(), nil
+}
+
+func TestKMSRegistry_LoadMemory(t *testing.T) {
+ registry := encryption.NewKMSRegistry()
+ kms, err := registry.Load(t.Context(), map[string]string{
encryption.KMSTypeKey: "memory",
})
require.NoError(t, err)
assert.IsType(t, &encryption.InMemoryKeyManagementClient{}, kms)
}
-func TestLoadKeyManagementClient_NoPropertiesSet(t *testing.T) {
- _, err := encryption.LoadKeyManagementClient(map[string]string{})
+func TestKMSRegistry_NoPropertiesSet(t *testing.T) {
+ _, err := encryption.NewKMSRegistry().Load(t.Context(),
map[string]string{})
require.ErrorIs(t, err, encryption.ErrKMSTypeNotFound)
}
-func TestLoadKeyManagementClient_UnregisteredName(t *testing.T) {
- _, err := encryption.LoadKeyManagementClient(map[string]string{
+func TestKMSRegistry_UnregisteredName(t *testing.T) {
+ _, err := encryption.NewKMSRegistry().Load(t.Context(),
map[string]string{
encryption.KMSTypeKey: "does-not-exist",
})
require.ErrorIs(t, err, encryption.ErrKMSTypeNotFound)
}
-func TestRegisterKMS_DuplicatePanics(t *testing.T) {
- const name = "test-duplicate-kms"
- factory := func(_ map[string]string) (encryption.KeyManagementClient,
error) {
- return encryption.NewInMemoryKeyManagementClient(), nil
- }
+func TestKMSRegistry_DuplicateRegistration(t *testing.T) {
+ registry := encryption.NewKMSRegistry()
+ require.NoError(t, registry.Register("custom", memoryKMSFactory))
+ err := registry.Register("custom", func(_ context.Context, _
map[string]string) (encryption.KeyManagementClient, error) {
+ return nil, assert.AnError
+ })
+ require.ErrorIs(t, err, encryption.ErrKMSAlreadyRegistered)
- encryption.RegisterKMS(name, factory)
- defer encryption.UnregisterKMS(name)
+ _, err = registry.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: "custom"})
+ require.NoError(t, err)
+}
- assert.Panics(t, func() {
- encryption.RegisterKMS(name, factory)
- })
+func TestKMSRegistry_InvalidRegistration(t *testing.T) {
+ registry := encryption.NewKMSRegistry()
+ require.ErrorIs(t, registry.Register("invalid", nil),
encryption.ErrInvalidKMSRegistration)
+ require.ErrorIs(t, registry.Register("", memoryKMSFactory),
encryption.ErrInvalidKMSRegistration)
+ assert.Equal(t, []string{"memory"}, registry.Names())
}
-func TestRegisterKMS_NilFactoryPanics(t *testing.T) {
- assert.Panics(t, func() {
- encryption.RegisterKMS("test-nil-factory-kms", nil)
- })
+func TestKMSRegistry_IsolatedRegistries(t *testing.T) {
+ first := encryption.NewKMSRegistry()
+ second := encryption.NewKMSRegistry()
+ require.NoError(t, first.Register("shared", memoryKMSFactory))
+ require.NoError(t, second.Register("shared", memoryKMSFactory))
+
+ first.Unregister("shared")
+ _, err := first.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: "shared"})
+ require.ErrorIs(t, err, encryption.ErrKMSTypeNotFound)
+
+ _, err = second.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: "shared"})
+ require.NoError(t, err)
+ assert.Equal(t, []string{"memory", "shared"}, second.Names())
}
-func TestGetRegisteredKMSNames_IncludesMemory(t *testing.T) {
- names := encryption.GetRegisteredKMSNames()
- assert.Contains(t, names, "memory")
+func TestKMSRegistry_NamesSortedAndIndependent(t *testing.T) {
+ registry := encryption.NewKMSRegistry()
+ require.NoError(t, registry.Register("z", memoryKMSFactory))
+ require.NoError(t, registry.Register("a", memoryKMSFactory))
+
+ names := registry.Names()
+ assert.Equal(t, []string{"a", "memory", "z"}, names)
+
+ names[0] = "changed"
+ assert.Equal(t, []string{"a", "memory", "z"}, registry.Names())
}
-func TestLoadKeyManagementClient_FactoryError(t *testing.T) {
- const name = "test-error-kms"
+func TestKMSRegistry_ZeroValue(t *testing.T) {
+ var registry encryption.KMSRegistry
+ assert.Empty(t, registry.Names())
+ require.NoError(t, registry.Register("custom", memoryKMSFactory))
+
+ _, err := registry.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: "custom"})
+ require.NoError(t, err)
+ registry.Unregister("custom")
+ assert.Empty(t, registry.Names())
+}
+
+func TestKMSRegistry_FactoryError(t *testing.T) {
+ registry := encryption.NewKMSRegistry()
wantErr := assert.AnError
- encryption.RegisterKMS(name, func(_ map[string]string)
(encryption.KeyManagementClient, error) {
+ require.NoError(t, registry.Register("error-kms", func(_
context.Context, _ map[string]string) (encryption.KeyManagementClient, error) {
return nil, wantErr
- })
- defer encryption.UnregisterKMS(name)
+ }))
- _, err := encryption.LoadKeyManagementClient(map[string]string{
- encryption.KMSTypeKey: name,
- })
+ _, err := registry.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: "error-kms"})
require.ErrorIs(t, err, wantErr)
}
-func TestLoadKeyManagementClient_PassesPropsToFactory(t *testing.T) {
- const name = "test-props-kms"
+func TestKMSRegistry_PassesContextToFactory(t *testing.T) {
+ type contextKey struct{}
+ ctx := context.WithValue(t.Context(), contextKey{}, "request")
+ registry := encryption.NewKMSRegistry()
+ called := false
+ require.NoError(t, registry.Register("ctx-kms", func(got
context.Context, _ map[string]string) (encryption.KeyManagementClient, error) {
+ called = true
+ assert.Equal(t, "request", got.Value(contextKey{}))
+
+ return encryption.NewInMemoryKeyManagementClient(), nil
+ }))
+
+ _, err := registry.Load(ctx, map[string]string{encryption.KMSTypeKey:
"ctx-kms"})
+ require.NoError(t, err)
+ assert.True(t, called, "factory must receive the caller context")
+}
+
+func TestKMSRegistry_PropagatesCanceledContext(t *testing.T) {
+ registry := encryption.NewKMSRegistry()
+ require.NoError(t, registry.Register("context-kms", func(ctx
context.Context, _ map[string]string) (encryption.KeyManagementClient, error) {
+ return nil, ctx.Err()
+ }))
+
+ ctx, cancel := context.WithCancel(t.Context())
+ cancel()
+
+ _, err := registry.Load(ctx, map[string]string{encryption.KMSTypeKey:
"context-kms"})
+ require.ErrorIs(t, err, context.Canceled)
+}
+
+func TestKMSRegistry_PassesPropertiesToFactory(t *testing.T) {
+ registry := encryption.NewKMSRegistry()
wantProps := map[string]string{
- encryption.KMSTypeKey: name,
+ encryption.KMSTypeKey: "props-kms",
"encryption.kms.region": "us-east-1",
}
var gotProps map[string]string
- encryption.RegisterKMS(name, func(props map[string]string)
(encryption.KeyManagementClient, error) {
+ require.NoError(t, registry.Register("props-kms", func(_
context.Context, props map[string]string) (encryption.KeyManagementClient,
error) {
gotProps = props
return encryption.NewInMemoryKeyManagementClient(), nil
+ }))
+
+ _, err := registry.Load(t.Context(), wantProps)
+ require.NoError(t, err)
+ assert.Equal(t, wantProps, gotProps)
+}
+
+func TestKMSRegistry_FactoryMayUpdateRegistry(t *testing.T) {
+ registry := encryption.NewKMSRegistry()
+ require.NoError(t, registry.Register("nested", func(_ context.Context,
_ map[string]string) (encryption.KeyManagementClient, error) {
+ if err := registry.Register("new-factory", memoryKMSFactory);
err != nil {
+ return nil, err
+ }
+
+ return encryption.NewInMemoryKeyManagementClient(), nil
+ }))
+
+ _, err := registry.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: "nested"})
+ require.NoError(t, err)
+ _, err = registry.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: "new-factory"})
+ require.NoError(t, err)
+}
+
+func TestKMSRegistry_ConcurrentUse(t *testing.T) {
+ registry := encryption.NewKMSRegistry()
+ const workerCount = 32
+
+ var wg sync.WaitGroup
+ failures := make(chan error, workerCount)
+ for i := range workerCount {
+ wg.Go(func() {
+ name := fmt.Sprintf("worker-%d", i)
+ if err := registry.Register(name, memoryKMSFactory);
err != nil {
+ failures <- err
+
+ return
+ }
+ if _, err := registry.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: name}); err != nil {
+ failures <- err
+ }
+ _ = registry.Names()
+ registry.Unregister(name)
+ })
+ }
+ wg.Wait()
+ close(failures)
+
+ for err := range failures {
+ require.NoError(t, err)
+ }
+}
+
+func TestKMSRegistry_DefaultRegistrySnapshot(t *testing.T) {
+ const name = "test-default-kms-registry-snapshot"
+ before := encryption.NewKMSRegistry()
+
+ // Mimic a provider registering through a side-effect import.
+ encryption.RegisterKMS(name, memoryKMSFactory)
+ t.Cleanup(func() { encryption.UnregisterKMS(name) })
+
+ after := encryption.NewKMSRegistry()
+ assert.Contains(t, encryption.GetRegisteredKMSNames(), name)
+
+ _, err := before.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: name})
+ require.ErrorIs(t, err, encryption.ErrKMSTypeNotFound)
+ _, err = after.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: name})
+ require.NoError(t, err)
+
+ // Removing a global registration must not mutate an existing snapshot.
+ encryption.UnregisterKMS(name)
+ _, err = after.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: name})
+ require.NoError(t, err)
+ _, err = encryption.NewKMSRegistry().Load(t.Context(),
map[string]string{encryption.KMSTypeKey: name})
+ require.ErrorIs(t, err, encryption.ErrKMSTypeNotFound)
+}
+
+func TestKMSRegistry_CloneIsIndependent(t *testing.T) {
+ registry := encryption.NewKMSRegistry()
+ clone := registry.Clone()
+ require.NoError(t, clone.Register("only-clone", memoryKMSFactory))
+
+ _, err := registry.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: "only-clone"})
+ require.ErrorIs(t, err, encryption.ErrKMSTypeNotFound)
+ _, err = clone.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: "only-clone"})
+ require.NoError(t, err)
+
+ registry.Unregister("memory")
+ _, err = clone.Load(t.Context(),
map[string]string{encryption.KMSTypeKey: "memory"})
+ require.NoError(t, err)
+
+ // Cloning the zero-value registry must also create an independent
instance.
+ var empty encryption.KMSRegistry
+ copied := empty.Clone()
+ require.NoError(t, copied.Register("custom", memoryKMSFactory))
+ assert.Empty(t, empty.Names())
+}
+
+func TestKMSRegistry_GlobalRegistrationCompatibility(t *testing.T) {
+ const name = "test-global-kms-registration"
+ encryption.MustRegisterKMS(name, memoryKMSFactory)
+ t.Cleanup(func() { encryption.UnregisterKMS(name) })
+
+ assert.Panics(t, func() {
+ encryption.RegisterKMS(name, func(_ context.Context, _
map[string]string) (encryption.KeyManagementClient, error) {
+ return nil, assert.AnError
+ })
})
- defer encryption.UnregisterKMS(name)
+ assert.Panics(t, func() { encryption.RegisterKMS("test-nil-global-kms",
nil) })
- _, err := encryption.LoadKeyManagementClient(wantProps)
+ kms, err := encryption.LoadKeyManagementClient(t.Context(),
map[string]string{encryption.KMSTypeKey: name})
require.NoError(t, err)
- assert.Equal(t, wantProps, gotProps, "the full props map given to
LoadKeyManagementClient must reach the factory unmodified")
+ assert.IsType(t, &encryption.InMemoryKeyManagementClient{}, kms)
+
+ // Registering only on an instance must not change the process-wide
default.
+ require.NoError(t,
encryption.NewKMSRegistry().Register("instance-only", memoryKMSFactory))
+ _, err = encryption.LoadKeyManagementClient(t.Context(),
map[string]string{encryption.KMSTypeKey: "instance-only"})
+ require.ErrorIs(t, err, encryption.ErrKMSTypeNotFound)
+}
+
+func TestKMSRegistry_GlobalLoaderPassesContext(t *testing.T) {
+ const name = "test-global-kms-context"
+ encryption.MustRegisterKMS(name, func(ctx context.Context, _
map[string]string) (encryption.KeyManagementClient, error) {
+ return nil, ctx.Err()
+ })
+ t.Cleanup(func() { encryption.UnregisterKMS(name) })
+
+ ctx, cancel := context.WithCancel(t.Context())
+ cancel()
+ _, err := encryption.LoadKeyManagementClient(ctx,
map[string]string{encryption.KMSTypeKey: name})
+ require.ErrorIs(t, err, context.Canceled)
}
diff --git a/website/src/releases.md b/website/src/releases.md
index 4cfc00695..c18fd3c6f 100644
--- a/website/src/releases.md
+++ b/website/src/releases.md
@@ -42,6 +42,7 @@ Per-release notes (highlights, breaking changes,
contributors) are published on
### Unreleased compatibility notes
+- **KMS factory registration now takes a context.** Change
`encryption.KMSFactory` implementations to accept `(context.Context,
map[string]string)` instead of only properties, and call
`encryption.LoadKeyManagementClient(ctx, props)` instead of
`LoadKeyManagementClient(props)`. The existing global `RegisterKMS`,
`UnregisterKMS`, and `GetRegisteredKMSNames` functions still use the default
registry; `RegisterKMS` still panics for invalid registrations. Use
`encryption.MustRegisterKMS` for [...]
- **Data file block size setter removed.**
`iceberg.DataFileBuilder.BlockSizeInBytes` is removed. New v1 manifests use the
Avro schema's 64 MiB default when the field is unset. V2/v3 omit this
deprecated field, and legacy v1 values remain readable.
- **View creation APIs take a named SQL representation.**
`view.NewVersionFromSQL`, `view.CreateView`, `view.CreateViewWithIOProperties`,
and `sql.Catalog.CreateView` now accept a `view.Representation{SQL: sql,
Dialect: dialect}` instead of positional SQL/dialect strings. The dialect is
required; Hive preserves the selected representation's dialect.