Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(collections): add Address and Integer codecs #22517

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions collections/indexing.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ func (c collectionImpl[K, V]) schemaCodec() (*collectionSchemaCodec, error) {
if err != nil {
return nil, err
}
if valueDecoder.ToSchemaType == nil {
return x, nil
}
return valueDecoder.ToSchemaType(x)
}
ensureFieldNames(c.m.vc, "value", res.objectType.ValueFields)
Expand Down
44 changes: 42 additions & 2 deletions collections/pair.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,17 +245,43 @@ func (p pairKeyCodec[K1, K2]) SchemaCodec() (codec.SchemaCodec[Pair[K1, K2]], er
return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key2 field: %w", err)
}

codec1, err := codec.KeySchemaCodec(p.keyCodec1)
if err != nil {
return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key1 schema codec: %w", err)
}

codec2, err := codec.KeySchemaCodec(p.keyCodec2)
if err != nil {
return codec.SchemaCodec[Pair[K1, K2]]{}, fmt.Errorf("error getting key2 schema codec: %w", err)
}

return codec.SchemaCodec[Pair[K1, K2]]{
Fields: []schema.Field{field1, field2},
ToSchemaType: func(pair Pair[K1, K2]) (any, error) {
return []interface{}{pair.K1(), pair.K2()}, nil
k1, err := toKeySchemaType(codec1, pair.K1())
if err != nil {
return nil, err
}
k2, err := toKeySchemaType(codec2, pair.K2())
if err != nil {
return nil, err
}
return []interface{}{k1, k2}, nil
},
FromSchemaType: func(a any) (Pair[K1, K2], error) {
aSlice, ok := a.([]interface{})
if !ok || len(aSlice) != 2 {
return Pair[K1, K2]{}, fmt.Errorf("expected slice of length 2, got %T", a)
}
return Join(aSlice[0].(K1), aSlice[1].(K2)), nil
k1, err := fromKeySchemaType(codec1, aSlice[0])
if err != nil {
return Pair[K1, K2]{}, err
}
k2, err := fromKeySchemaType(codec2, aSlice[1])
if err != nil {
return Pair[K1, K2]{}, err
}
return Join(k1, k2), nil
},
}, nil
}
Expand All @@ -273,6 +299,20 @@ func getNamedKeyField[T any](keyCdc codec.KeyCodec[T], name string) (schema.Fiel
return field, nil
}

func toKeySchemaType[T any](cdc codec.SchemaCodec[T], key T) (any, error) {
if cdc.ToSchemaType != nil {
return cdc.ToSchemaType(key)
}
return key, nil
}

func fromKeySchemaType[T any](cdc codec.SchemaCodec[T], key any) (T, error) {
if cdc.FromSchemaType != nil {
return cdc.FromSchemaType(key)
}
return key.(T), nil
}
Comment on lines +309 to +314
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle type assertions safely to prevent potential panics

In the fromKeySchemaType function, the type assertion key.(T) may cause a panic if key is not of type T. Use the comma-ok idiom to safely perform the type assertion and handle the error accordingly.

Here's how you can modify the code:

 func fromKeySchemaType[T any](cdc codec.SchemaCodec[T], key any) (T, error) {
     if cdc.FromSchemaType != nil {
         return cdc.FromSchemaType(key)
     }
-    return key.(T), nil
+    tKey, ok := key.(T)
+    if !ok {
+        var zero T
+        return zero, fmt.Errorf("expected type %T, got %T", zero, key)
+    }
+    return tKey, nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func fromKeySchemaType[T any](cdc codec.SchemaCodec[T], key any) (T, error) {
if cdc.FromSchemaType != nil {
return cdc.FromSchemaType(key)
}
return key.(T), nil
}
func fromKeySchemaType[T any](cdc codec.SchemaCodec[T], key any) (T, error) {
if cdc.FromSchemaType != nil {
return cdc.FromSchemaType(key)
}
tKey, ok := key.(T)
if !ok {
var zero T
return zero, fmt.Errorf("expected type %T, got %T", zero, key)
}
return tKey, nil
}


// NewPrefixUntilPairRange defines a collection query which ranges until the provided Pair prefix.
// Unstable: this API might change in the future.
func NewPrefixUntilPairRange[K1, K2 any](prefix K1) *PairRange[K1, K2] {
Expand Down
7 changes: 4 additions & 3 deletions indexer/postgres/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,11 @@ func (tm *objectIndexer) bindParam(field schema.Field, value interface{}) (param

param = int64(t)
} else if field.Kind == schema.AddressKind {
param, err = tm.options.addressCodec.BytesToString(value.([]byte))
if err != nil {
return nil, fmt.Errorf("address encoding failed for field %q: %w", field.Name, err)
t, ok := value.(string)
if !ok {
return nil, fmt.Errorf("expected string value for field %q, got %T", field.Name, value)
}
param = t
}
return
}
1 change: 0 additions & 1 deletion schema/kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,6 @@ func (t Kind) ValidateValueType(value interface{}) error {
if !ok {
return fmt.Errorf("expected string, got %T", value)
}

case DecimalKind:
_, ok := value.(string)
if !ok {
Expand Down
38 changes: 38 additions & 0 deletions types/collections.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"cosmossdk.io/collections"
collcodec "cosmossdk.io/collections/codec"
"cosmossdk.io/math"
"cosmossdk.io/schema"
)

var (
Expand Down Expand Up @@ -120,6 +121,23 @@ func (a genericAddressKey[T]) SizeNonTerminal(key T) int {
return collections.BytesKey.SizeNonTerminal(key)
}

func (a genericAddressKey[T]) SchemaCodec() (collcodec.SchemaCodec[T], error) {
return collcodec.SchemaCodec[T]{
Fields: []schema.Field{{Kind: schema.AddressKind}},
ToSchemaType: func(t T) (any, error) {
return t.String(), nil
},
FromSchemaType: func(s any) (T, error) {
var t T
sz, ok := s.(string)
if !ok {
return t, fmt.Errorf("expected string, got %T", s)
}
return a.stringDecoder(sz)
},
}, nil
}

// Deprecated: lengthPrefixedAddressKey is a special key codec used to retain state backwards compatibility
// when a generic address key (be: AccAddress, ValAddress, ConsAddress), is used as an index key.
// More docs can be found in the LengthPrefixedAddressKey function.
Expand Down Expand Up @@ -214,6 +232,26 @@ func (i intValueCodec) ValueType() string {
return Int
}

func (i intValueCodec) SchemaCodec() (collcodec.SchemaCodec[math.Int], error) {
return collcodec.SchemaCodec[math.Int]{
Fields: []schema.Field{{Kind: schema.IntegerKind}},
ToSchemaType: func(t math.Int) (any, error) {
return t.String(), nil
},
FromSchemaType: func(s any) (math.Int, error) {
sz, ok := s.(string)
if !ok {
return math.Int{}, fmt.Errorf("expected string, got %T", s)
}
t, ok := math.NewIntFromString(sz)
if !ok {
return math.Int{}, fmt.Errorf("failed to parse Int from string: %s", sz)
}
return t, nil
},
}, nil
}

type uintValueCodec struct{}

func (i uintValueCodec) Encode(value math.Uint) ([]byte, error) {
Expand Down
Loading