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: Reverted order for indexed fields #2335

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d72cfb9
СockroachDB pkg/util/encoding package
islamaliev Feb 23, 2024
d419535
Implement basic encoding, use it for secondary indexes
islamaliev Feb 20, 2024
b209392
Add descending order
islamaliev Feb 21, 2024
6e6c7ce
Enable reverted order for single index field
islamaliev Feb 21, 2024
4998927
Add tests for parsing
islamaliev Feb 21, 2024
f97c54d
Rename receiver
islamaliev Feb 21, 2024
21e09f4
Fix lint
islamaliev Feb 21, 2024
b9df777
Fix lint
islamaliev Feb 21, 2024
7320ce9
Set proper test description
islamaliev Feb 21, 2024
3614781
Make change detector happy
islamaliev Feb 21, 2024
ce730e9
Remove fieldID from encoding into secondary index
islamaliev Feb 22, 2024
05942a2
Remove unused code, make errors consistent
islamaliev Feb 22, 2024
02207f7
Legitimize
islamaliev Feb 22, 2024
531b1f2
Fix after rebase
islamaliev Feb 23, 2024
da050a7
Switch to Apache License
islamaliev Feb 23, 2024
584e95e
Move encoding package to the top level
islamaliev Feb 23, 2024
1e5f2eb
Exclude encoding from license check
islamaliev Feb 23, 2024
efd6cb7
Fix deprecated warning
islamaliev Feb 23, 2024
6ddd2cd
Remove nil check in FieldValue
islamaliev Feb 23, 2024
888679f
Refactor: remove kind from encoded key, pass around value itself inst…
islamaliev Feb 27, 2024
c992cfc
Make 'fields' private field of IndexDataStoreKey
islamaliev Feb 28, 2024
d51ba83
Use existing value normalization function
islamaliev Feb 28, 2024
a776d35
Remove kind from FieldValue
islamaliev Feb 28, 2024
7b09fb3
Adjust tests
islamaliev Feb 29, 2024
0ef6fbc
Check err on AppendField
islamaliev Feb 29, 2024
18ff5f8
Polish
islamaliev Feb 29, 2024
e4dba94
Move consts
islamaliev Feb 29, 2024
79f99b4
Remove r from DecodeBytes
islamaliev Feb 29, 2024
151da8a
Move function to encoding file
islamaliev Feb 29, 2024
beb165f
Polish
islamaliev Feb 29, 2024
c0553e8
Adjustments after rebase
islamaliev Feb 29, 2024
b655f2a
Fix tests
islamaliev Feb 29, 2024
bc6d5d6
Polish
islamaliev Feb 29, 2024
fa956bd
Test another case
islamaliev Feb 29, 2024
d51a6fb
Remove kind from client.Field
islamaliev Feb 29, 2024
5c1d1c2
Add tests for index key field-related methods
islamaliev Feb 29, 2024
c0776e0
Make IndexDataStoreKey with public 'FIelds'
islamaliev Feb 29, 2024
cfd784a
Polish
islamaliev Feb 29, 2024
b0386b0
Fix lint
islamaliev Feb 29, 2024
d56112c
Fix mutation of passed slice
islamaliev Mar 1, 2024
ad348a8
PR fix up
islamaliev Mar 1, 2024
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
5 changes: 3 additions & 2 deletions client/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ func NewDocsFromJSON(obj []byte, sd SchemaDescription) ([]*Document, error) {
return docs, nil
}

func isNillableKind(kind FieldKind) bool {
// IsNillableKind returns true if the given FieldKind is nillable.
func IsNillableKind(kind FieldKind) bool {
switch kind {
case FieldKind_NILLABLE_STRING, FieldKind_NILLABLE_BLOB, FieldKind_NILLABLE_JSON,
FieldKind_NILLABLE_BOOL, FieldKind_NILLABLE_FLOAT, FieldKind_NILLABLE_DATETIME,
Expand All @@ -188,7 +189,7 @@ func isNillableKind(kind FieldKind) bool {
// It will do any minor parsing, like dates, and return
// the typed value again as an interface.
func validateFieldSchema(val any, field SchemaFieldDescription) (any, error) {
if isNillableKind(field.Kind) {
if IsNillableKind(field.Kind) {
if val == nil {
return nil, nil
}
Expand Down
16 changes: 3 additions & 13 deletions client/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,18 @@ package client
type Field interface {
Name() string
Type() CType //TODO Abstract into a Field Type interface
SchemaType() string
}

type simpleField struct {
name string
crdtType CType
schemaType string
name string
crdtType CType
}

func (doc *Document) newField(t CType, name string, schemaType ...string) Field {
func (doc *Document) newField(t CType, name string) Field {
f := simpleField{
name: name,
crdtType: t,
}
if len(schemaType) > 0 {
f.schemaType = schemaType[0]
}
return f
}

Expand All @@ -43,8 +38,3 @@ func (field simpleField) Name() string {
func (field simpleField) Type() CType {
return field.crdtType
}

// SchemaType returns the schema type of the field.
func (field simpleField) SchemaType() string {
return field.schemaType
}
14 changes: 2 additions & 12 deletions client/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,12 @@

package client

// IndexDirection is the direction of an index.
type IndexDirection string

const (
// Ascending is the value to use for an ascending fields
Ascending IndexDirection = "ASC"
// Descending is the value to use for an descending fields
Descending IndexDirection = "DESC"
)

// IndexFieldDescription describes how a field is being indexed.
type IndexedFieldDescription struct {
// Name contains the name of the field.
Name string
// Direction contains the direction of the index.
Direction IndexDirection
// Descending indicates whether the field is indexed in descending order.
Descending bool
}

// IndexDescription describes an index.
Expand Down
18 changes: 9 additions & 9 deletions client/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestCollectIndexesOnField(t *testing.T) {
{
Name: "index1",
Fields: []IndexedFieldDescription{
{Name: "test", Direction: Ascending},
{Name: "test"},
},
},
},
Expand All @@ -48,7 +48,7 @@ func TestCollectIndexesOnField(t *testing.T) {
{
Name: "index1",
Fields: []IndexedFieldDescription{
{Name: "test", Direction: Ascending},
{Name: "test"},
},
},
},
Expand All @@ -60,13 +60,13 @@ func TestCollectIndexesOnField(t *testing.T) {
{
Name: "index1",
Fields: []IndexedFieldDescription{
{Name: "test", Direction: Ascending},
{Name: "test"},
},
},
{
Name: "index2",
Fields: []IndexedFieldDescription{
{Name: "test", Direction: Descending},
{Name: "test", Descending: true},
},
},
},
Expand All @@ -76,13 +76,13 @@ func TestCollectIndexesOnField(t *testing.T) {
{
Name: "index1",
Fields: []IndexedFieldDescription{
{Name: "test", Direction: Ascending},
{Name: "test"},
},
},
{
Name: "index2",
Fields: []IndexedFieldDescription{
{Name: "test", Direction: Descending},
{Name: "test", Descending: true},
},
},
},
Expand All @@ -94,7 +94,7 @@ func TestCollectIndexesOnField(t *testing.T) {
{
Name: "index1",
Fields: []IndexedFieldDescription{
{Name: "other", Direction: Ascending},
{Name: "other"},
},
},
},
Expand All @@ -109,8 +109,8 @@ func TestCollectIndexesOnField(t *testing.T) {
{
Name: "index1",
Fields: []IndexedFieldDescription{
{Name: "other", Direction: Ascending},
{Name: "test", Direction: Ascending},
{Name: "other"},
{Name: "test"},
},
},
},
Expand Down
139 changes: 137 additions & 2 deletions core/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
"github.com/sourcenetwork/immutable"

"github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/client/request"
"github.com/sourcenetwork/defradb/encoding"
)

// DecodeFieldValue takes a field value and description and converts it to the
// NormalizeFieldValue takes a field value and description and converts it to the
// standardized Defra Go type.
func DecodeFieldValue(fieldDesc client.FieldDefinition, val any) (any, error) {
func NormalizeFieldValue(fieldDesc client.FieldDefinition, val any) (any, error) {
if val == nil {
return nil, nil
}
Expand Down Expand Up @@ -125,6 +127,16 @@
case string:
return time.Parse(time.RFC3339, v)
}
case client.FieldKind_NILLABLE_BOOL:
switch v := val.(type) {
case int64:
return v != 0, nil

Check warning on line 133 in core/encoding.go

View check run for this annotation

Codecov / codecov/patch

core/encoding.go#L132-L133

Added lines #L132 - L133 were not covered by tests
}
case client.FieldKind_NILLABLE_STRING:
switch v := val.(type) {
case []byte:
return string(v), nil
}
}
}

Expand Down Expand Up @@ -179,3 +191,126 @@
return 0, client.NewErrUnexpectedType[string](propertyName, untypedValue)
}
}

// DecodeIndexDataStoreKey decodes a IndexDataStoreKey from bytes.
// It expects the input bytes is in the following format:
//
// /[CollectionID]/[IndexID]/[FieldValue](/[FieldValue]...)
//
// Where [CollectionID] and [IndexID] are integers
//
// All values of the fields are converted to standardized Defra Go type
// according to fields description.
func DecodeIndexDataStoreKey(
data []byte,
indexDesc *client.IndexDescription,
fields []client.FieldDefinition,
) (IndexDataStoreKey, error) {
if len(data) == 0 {
return IndexDataStoreKey{}, ErrEmptyKey
}

if data[0] != '/' {
return IndexDataStoreKey{}, ErrInvalidKey
}
data = data[1:]

data, colID, err := encoding.DecodeUvarintAscending(data)
if err != nil {
return IndexDataStoreKey{}, err
}

key := IndexDataStoreKey{CollectionID: uint32(colID)}

if data[0] != '/' {
return IndexDataStoreKey{}, ErrInvalidKey
}
data = data[1:]

data, indID, err := encoding.DecodeUvarintAscending(data)
if err != nil {
return IndexDataStoreKey{}, err
}
key.IndexID = uint32(indID)

if len(data) == 0 {
return key, nil
}

Check warning on line 238 in core/encoding.go

View check run for this annotation

Codecov / codecov/patch

core/encoding.go#L237-L238

Added lines #L237 - L238 were not covered by tests

for len(data) > 0 {
if data[0] != '/' {
return IndexDataStoreKey{}, ErrInvalidKey
}
data = data[1:]

i := len(key.Fields)
descending := false
// If the key has more values encoded then fields on the index description, the last
// value must be the docID and we treat it as a string.
if i < len(indexDesc.Fields) {
descending = indexDesc.Fields[i].Descending
} else if i > len(indexDesc.Fields) {
return IndexDataStoreKey{}, ErrInvalidKey
}

var val any
data, val, err = encoding.DecodeFieldValue(data, descending)
if err != nil {
return IndexDataStoreKey{}, err
}

key.Fields = append(key.Fields, IndexedField{Value: val, Descending: descending})
}

err = normalizeIndexDataStoreKeyValues(&key, fields)
return key, err
}

// normalizeIndexDataStoreKeyValues converts all field values to standardized
// Defra Go type according to fields description.
func normalizeIndexDataStoreKeyValues(key *IndexDataStoreKey, fields []client.FieldDefinition) error {
for i := range key.Fields {
if key.Fields[i].Value == nil {
continue
}
var err error
var val any
if i == len(key.Fields)-1 && len(key.Fields)-len(fields) == 1 {
bytes, ok := key.Fields[i].Value.([]byte)
if !ok {
return client.NewErrUnexpectedType[[]byte](request.DocIDArgName, key.Fields[i].Value)
}
val = string(bytes)
} else {
val, err = NormalizeFieldValue(fields[i], key.Fields[i].Value)
}
if err != nil {
return err
}

Check warning on line 289 in core/encoding.go

View check run for this annotation

Codecov / codecov/patch

core/encoding.go#L288-L289

Added lines #L288 - L289 were not covered by tests
key.Fields[i].Value = val
}
return nil
}

// EncodeIndexDataStoreKey encodes a IndexDataStoreKey to bytes to be stored as a key
// for secondary indexes.
func EncodeIndexDataStoreKey(key *IndexDataStoreKey) []byte {
if key.CollectionID == 0 {
return []byte{}
}

b := encoding.EncodeUvarintAscending([]byte{'/'}, uint64(key.CollectionID))

if key.IndexID == 0 {
return b
}
b = append(b, '/')
b = encoding.EncodeUvarintAscending(b, uint64(key.IndexID))

for _, field := range key.Fields {
b = append(b, '/')
b = encoding.EncodeFieldValue(b, field.Value, field.Descending)
}

return b
}
7 changes: 7 additions & 0 deletions core/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,22 @@

const (
errFailedToGetFieldIdOfKey string = "failed to get FieldID of Key"
errInvalidFieldIndex string = "invalid field index"
)

var (
ErrFailedToGetFieldIdOfKey = errors.New(errFailedToGetFieldIdOfKey)
ErrEmptyKey = errors.New("received empty key string")
ErrInvalidKey = errors.New("invalid key string")
ErrInvalidFieldIndex = errors.New(errInvalidFieldIndex)
)

// NewErrFailedToGetFieldIdOfKey returns the error indicating failure to get FieldID of Key.
func NewErrFailedToGetFieldIdOfKey(inner error) error {
return errors.Wrap(errFailedToGetFieldIdOfKey, inner)
}

// NewErrInvalidFieldIndex returns the error indicating invalid field index.
func NewErrInvalidFieldIndex(i int) error {
return errors.New(errInvalidFieldIndex, errors.NewKV("index", i))

Check warning on line 36 in core/errors.go

View check run for this annotation

Codecov / codecov/patch

core/errors.go#L35-L36

Added lines #L35 - L36 were not covered by tests
}
Loading
Loading