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: Non-unique secondary index #1450

Merged
merged 120 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
ca6722f
Parse index with single field
islamaliev Apr 24, 2023
989fca8
Add some error checking while parsing
islamaliev Apr 25, 2023
e2e5250
Parse multiple fields for composite index
islamaliev Apr 25, 2023
71f72cf
Add some more parsing checks
islamaliev Apr 25, 2023
f1c96af
Return slice of slices for index descriptions
islamaliev Apr 26, 2023
44c8e05
parse field index
islamaliev Apr 26, 2023
cdca6ad
Add validation of some index properties
islamaliev May 1, 2023
b68e41f
Store collection index description
islamaliev May 1, 2023
02036a1
Generate unique index name
islamaliev May 2, 2023
9374bbb
Test storing to system storage
islamaliev May 2, 2023
36932ea
Add tests for index key
islamaliev May 2, 2023
ec2d761
Add index key from string
islamaliev May 2, 2023
d2f7f4a
Add retrieving list of indexes
islamaliev May 3, 2023
4fdc121
Add retrieving list of collection indexes
islamaliev May 3, 2023
dcf11c3
Add storage failure tests
islamaliev May 3, 2023
0d848ab
Check if stored index is valid
islamaliev May 3, 2023
a9b0639
Check if indexes property exists
islamaliev May 3, 2023
1035215
Check storage errors
islamaliev May 3, 2023
0d75f58
Add drop index
islamaliev May 4, 2023
ac7a6f5
Add drop all indexes
islamaliev May 4, 2023
6e7769c
Remove DropAllIndexes from public interface
islamaliev May 5, 2023
819da19
Return error instead of stack
islamaliev May 5, 2023
020e86a
Rename CollectionID to CollectionName
islamaliev May 5, 2023
b02eb0a
Add custom asserter to test framework
islamaliev May 5, 2023
fdbffe5
Prepare fixtures for index testing
islamaliev May 8, 2023
f952210
Move index tests
islamaliev May 8, 2023
71be416
Add IndexDataStoreKey
islamaliev May 8, 2023
7356099
Add NewIndexDataStoreKey method
islamaliev May 8, 2023
6b16651
Add base implementation of indexing a doc
islamaliev May 9, 2023
9a20691
Create mocks for Txn and related interfaces
islamaliev May 10, 2023
cbbdbfb
Add test for index that uses mocks
islamaliev May 10, 2023
fa4355a
Skip indexing if doc has no field
islamaliev May 10, 2023
2117bf9
Check if system storage to read index description
islamaliev May 10, 2023
91de275
Index int fields
islamaliev May 11, 2023
d569f2d
Handle case sensitivity
islamaliev May 12, 2023
05e13aa
Save index with right collection ID
islamaliev May 12, 2023
21e305d
Assign incremented id to a new index
islamaliev May 15, 2023
37ae5e9
Index a new doc with multiple indexes
islamaliev May 15, 2023
f4699d8
Store indexed fields of different types
islamaliev May 16, 2023
39e19fd
Implement GetIndexes method
islamaliev May 17, 2023
5f6e340
Check if failed to create a new txn
islamaliev May 17, 2023
5d3d681
Use cached values instead of system storage
islamaliev May 17, 2023
af4338a
Make index check if doc is indexable
islamaliev May 17, 2023
89c39de
Update cache on new index and on deleting an index
islamaliev May 19, 2023
89a5faf
Check if sequence fetching failed
islamaliev May 19, 2023
45f72fa
Check if sequence incremented successfully
islamaliev May 19, 2023
e96fe21
Polish
islamaliev May 22, 2023
0e6ad32
Use "v" prefix for field values
islamaliev May 22, 2023
e7dfc43
Store nil index field with a special value
islamaliev May 22, 2023
d165c5d
Add test for empty string
islamaliev May 22, 2023
c27e6db
Remove indexed fields upon dropping index
islamaliev May 23, 2023
99b999c
Rename tests
islamaliev May 23, 2023
9446ca0
Extract prefix deserialization
islamaliev May 23, 2023
04d4c90
Update index in doc update
islamaliev May 26, 2023
d995afa
Move index related code to another file
islamaliev May 26, 2023
bedd748
Add EncodedDocument interface to fetcher.
islamaliev May 29, 2023
ee253ee
Check if fetcher returns errors
islamaliev May 29, 2023
f9f2b47
Check more errors on index update
islamaliev May 29, 2023
7320444
Pass relevant fields to fetcher
islamaliev May 29, 2023
b7c04d9
Use existing method to fetch a doc
islamaliev May 29, 2023
f52ba4a
Check more errors
islamaliev May 30, 2023
ea959b4
Include IndexDescription into CollectionDescription
islamaliev May 30, 2023
fa6d51c
Add documentation to index description
islamaliev May 30, 2023
836046a
Minor polish
islamaliev May 30, 2023
02c0e60
Remove accident files
islamaliev May 30, 2023
05ae66a
Remove unrelated changes
islamaliev May 30, 2023
e12f57c
Add "mocks" folder to ignore list of codecov
islamaliev May 30, 2023
8298eeb
Fix linter issues
islamaliev May 31, 2023
4598c17
Soft imports
islamaliev May 31, 2023
0f14e7d
Add copyright header
islamaliev May 31, 2023
0e1f1d0
Fix linter issues
islamaliev May 31, 2023
f53ccab
Add more tests and small fixes
islamaliev May 31, 2023
2a5fc79
Add missing test
islamaliev Jun 1, 2023
af2b964
Polish
islamaliev Jun 1, 2023
aeccb8c
Add missing tests
islamaliev Jun 1, 2023
8c770e1
Add more tests
islamaliev Jun 2, 2023
14f01cf
Small refactoring
islamaliev Jun 2, 2023
5466669
Add readme for tests
islamaliev Jun 2, 2023
b869790
Make struct private for db package
islamaliev Jun 2, 2023
a0c759b
Remove unique
islamaliev Jun 2, 2023
29ce194
Index existing docs on index creation
islamaliev Jun 5, 2023
b96c1df
Small refactor
islamaliev Jun 5, 2023
042356c
Add test for updating indexed nil values
islamaliev Jun 5, 2023
98e3ce7
Add documentation
islamaliev Jun 5, 2023
f8cc5e5
Add documentation
islamaliev Jun 6, 2023
ac5328d
Add integration test
islamaliev Jun 6, 2023
8cf92b6
Add integration tests for creating and dropping index
islamaliev Jun 9, 2023
d52f309
Extract validation of collection fields update
islamaliev Jun 9, 2023
3c0ab14
Check for index patching
islamaliev Jun 12, 2023
30456c1
Add check if patches change index fields
islamaliev Jun 12, 2023
9345870
Switch index values from string to []byte
islamaliev Jun 12, 2023
a16ff58
Format
islamaliev Jun 12, 2023
5b95288
Switch IndexDataStoreKey members to uint32
islamaliev Jun 12, 2023
a9662d8
Format
islamaliev Jun 12, 2023
a2c1c26
Small refactor
islamaliev Jun 13, 2023
fafc54a
Make q.Close() be called directly instead of defer
islamaliev Jun 13, 2023
f0a79f0
Remove deferred call to Query.Close()
islamaliev Jun 13, 2023
52076d9
Return UnsupportedType error instead of panicking
islamaliev Jun 14, 2023
85e69fb
Correctly read FieldID
islamaliev Jun 14, 2023
269b67c
Separate iteration over keys from deletion
islamaliev Jun 14, 2023
a9b1975
Fix key decomposition
islamaliev Jun 14, 2023
1c3e2ee
Make order of reading index descriptions deterministic
islamaliev Jun 14, 2023
b2ecc39
Polish
islamaliev Jun 14, 2023
c0343ad
Polish
islamaliev Jun 15, 2023
448cf93
Add missing test
islamaliev Jun 15, 2023
6e01caf
Store indexed values using CBOR encoding
islamaliev Jun 15, 2023
806602d
Remove index cache from collection
islamaliev Jun 16, 2023
93cae45
Add integration test for dropping non-existing index
islamaliev Jun 16, 2023
3b445c3
Move directive consts
islamaliev Jun 19, 2023
3af3fd2
Add @index directive to graphql schema
islamaliev Jun 19, 2023
4e639c2
Move newFetcher
islamaliev Jun 19, 2023
4560894
Split tests
islamaliev Jun 19, 2023
9793ac2
Add integration tests for getting indexes
islamaliev Jun 19, 2023
a635cb1
Update copyright
islamaliev Jun 19, 2023
feb420b
Add a comment
islamaliev Jun 19, 2023
6e24502
Format
islamaliev Jun 19, 2023
bfcd6b7
Add documentation to public methods
islamaliev Jun 19, 2023
5b4c60d
Make checking fields case sensitive
islamaliev Jun 19, 2023
b458b6d
Add more documentation
islamaliev Jun 19, 2023
3919472
Add missing test cases
islamaliev Jun 19, 2023
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
1 change: 1 addition & 0 deletions .github/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,6 @@ comment:

ignore:
- "tests"
- "**/mocks/*"
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
- "**/*_test.go"
- "**/*.pb.go"
13 changes: 13 additions & 0 deletions client/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,19 @@ type Collection interface {

// GetAllDocKeys returns all the document keys that exist in the collection.
GetAllDocKeys(ctx context.Context) (<-chan DocKeysResult, error)

// CreateIndex creates a new index on the collection.
// `IndexDescription` contains the description of the index to be created.
// `IndexDescription.Name` must start with a letter or an underscore and can
// only contain letters, numbers, and underscores.
// If the name of the index is not provided, it will be generated.
CreateIndex(context.Context, IndexDescription) (IndexDescription, error)

islamaliev marked this conversation as resolved.
Show resolved Hide resolved
// DropIndex drops an index from the collection.
DropIndex(ctx context.Context, indexName string) error

// GetIndexes returns all the indexes that exist on the collection.
GetIndexes(ctx context.Context) ([]IndexDescription, error)
}

// DocKeysResult wraps the result of an attempt at a DocKey retrieval operation.
Expand Down
7 changes: 5 additions & 2 deletions client/descriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ type CollectionDescription struct {

// Schema contains the data type information that this Collection uses.
Schema SchemaDescription

// Indexes contains the secondary indexes that this Collection has.
Indexes []IndexDescription
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
}

// IDString returns the collection ID as a string.
Expand All @@ -50,10 +53,10 @@ func (col CollectionDescription) GetField(name string) (FieldDescription, bool)

// GetFieldByID searches for a field with the given ID. If such a field is found it
// will return it and true, if it is not found it will return false.
func (col CollectionDescription) GetFieldByID(id string) (FieldDescription, bool) {
func (col CollectionDescription) GetFieldByID(id FieldID) (FieldDescription, bool) {
if !col.Schema.IsEmpty() {
for _, field := range col.Schema.Fields {
if field.ID.String() == id {
if field.ID == id {
return field, true
}
}
Expand Down
39 changes: 39 additions & 0 deletions client/index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2023 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package client

// IndexDirection is the direction of an index.
type IndexDirection string
fredcarle marked this conversation as resolved.
Show resolved Hide resolved

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 {
fredcarle marked this conversation as resolved.
Show resolved Hide resolved
// Name contains the name of the field.
Name string
// Direction contains the direction of the index.
Direction IndexDirection
}

// IndexDescription describes an index.
type IndexDescription struct {
fredcarle marked this conversation as resolved.
Show resolved Hide resolved
// Name contains the name of the index.
Name string
// ID is the local identifier of this index.
ID uint32
// Fields contains the fields that are being indexed.
Fields []IndexedFieldDescription
}
176 changes: 176 additions & 0 deletions core/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
COLLECTION = "/collection/names"
COLLECTION_SCHEMA = "/collection/schema"
COLLECTION_SCHEMA_VERSION = "/collection/version"
COLLECTION_INDEX = "/collection/index"
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
SEQ = "/seq"
PRIMARY_KEY = "/pk"
REPLICATOR = "/replicator/id"
Expand All @@ -67,6 +68,18 @@ type DataStoreKey struct {

var _ Key = (*DataStoreKey)(nil)

// IndexDataStoreKey is key of an indexed document in the database.
type IndexDataStoreKey struct {
// CollectionID is the id of the collection
CollectionID uint32
// IndexID is the id of the index
IndexID uint32
// FieldValues is the values of the fields in the index
FieldValues [][]byte
}

var _ Key = (*IndexDataStoreKey)(nil)

type PrimaryDataStoreKey struct {
CollectionId string
DocKey string
Expand Down Expand Up @@ -106,6 +119,16 @@ type CollectionSchemaVersionKey struct {

var _ Key = (*CollectionSchemaVersionKey)(nil)

// CollectionIndexKey to a stored description of an index
type CollectionIndexKey struct {
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
// CollectionName is the name of the collection that the index is on
CollectionName string
// IndexName is the name of the index
IndexName string
}

var _ Key = (*CollectionIndexKey)(nil)

type P2PCollectionKey struct {
CollectionID string
}
Expand Down Expand Up @@ -210,6 +233,56 @@ func NewCollectionSchemaVersionKey(schemaVersionId string) CollectionSchemaVersi
return CollectionSchemaVersionKey{SchemaVersionId: schemaVersionId}
}

// NewCollectionIndexKey creates a new CollectionIndexKey from a collection name and index name.
func NewCollectionIndexKey(colID, indexName string) CollectionIndexKey {
return CollectionIndexKey{CollectionName: colID, IndexName: indexName}
}

// NewCollectionIndexKeyFromString creates a new CollectionIndexKey from a string.
// It expects the input string is in the following format:
//
// /collection/index/[CollectionName]/[IndexName]
//
// Where [IndexName] might be omitted. Anything else will return an error.
func NewCollectionIndexKeyFromString(key string) (CollectionIndexKey, error) {
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
keyArr := strings.Split(key, "/")
if len(keyArr) < 4 || len(keyArr) > 5 || keyArr[1] != "collection" || keyArr[2] != "index" {
return CollectionIndexKey{}, ErrInvalidKey
}
result := CollectionIndexKey{CollectionName: keyArr[3]}
if len(keyArr) == 5 {
result.IndexName = keyArr[4]
}
return result, nil
}

// ToString returns the string representation of the key
// It is in the following format:
// /collection/index/[CollectionName]/[IndexName]
// if [CollectionName] is empty, the rest is ignored
func (k CollectionIndexKey) ToString() string {
result := COLLECTION_INDEX

if k.CollectionName != "" {
result = result + "/" + k.CollectionName
if k.IndexName != "" {
result = result + "/" + k.IndexName
}
}

return result
}

// Bytes returns the byte representation of the key
func (k CollectionIndexKey) Bytes() []byte {
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
return []byte(k.ToString())
}

// ToDS returns the datastore key
func (k CollectionIndexKey) ToDS() ds.Key {
return ds.NewKey(k.ToString())
}

func NewSequenceKey(name string) SequenceKey {
return SequenceKey{SequenceName: name}
}
Expand Down Expand Up @@ -318,6 +391,109 @@ func (k DataStoreKey) ToPrimaryDataStoreKey() PrimaryDataStoreKey {
}
}

// NewIndexDataStoreKey creates a new IndexDataStoreKey from a string.
// It expects the input string is in the following format:
//
// /[CollectionID]/[IndexID]/[FieldValue](/[FieldValue]...)
//
// Where [CollectionID] and [IndexID] are integers
func NewIndexDataStoreKey(key string) (IndexDataStoreKey, error) {
if key == "" {
return IndexDataStoreKey{}, ErrEmptyKey
}

if !strings.HasPrefix(key, "/") {
return IndexDataStoreKey{}, ErrInvalidKey
}

elements := strings.Split(key[1:], "/")

// With less than 3 elements, we know it's an invalid key
if len(elements) < 3 {
return IndexDataStoreKey{}, ErrInvalidKey
}

colID, err := strconv.Atoi(elements[0])
if err != nil {
return IndexDataStoreKey{}, ErrInvalidKey
}

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

indID, err := strconv.Atoi(elements[1])
if err != nil {
return IndexDataStoreKey{}, ErrInvalidKey
}
indexKey.IndexID = uint32(indID)

// first 2 elements are the collection and index IDs, the rest are field values
for i := 2; i < len(elements); i++ {
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
indexKey.FieldValues = append(indexKey.FieldValues, []byte(elements[i]))
}

return indexKey, nil
}

// Bytes returns the byte representation of the key
func (k *IndexDataStoreKey) Bytes() []byte {
return []byte(k.ToString())
}

// ToDS returns the datastore key
func (k *IndexDataStoreKey) ToDS() ds.Key {
return ds.NewKey(k.ToString())
}

// ToString returns the string representation of the key
// It is in the following format:
// /[CollectionID]/[IndexID]/[FieldValue](/[FieldValue]...)
// If while composing the string from left to right, a component
// is empty, the string is returned up to that point
func (k *IndexDataStoreKey) ToString() string {
sb := strings.Builder{}

if k.CollectionID == 0 {
return ""
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
}
sb.WriteByte('/')
sb.WriteString(strconv.Itoa(int(k.CollectionID)))

if k.IndexID == 0 {
return sb.String()
}
sb.WriteByte('/')
sb.WriteString(strconv.Itoa(int(k.IndexID)))

for _, v := range k.FieldValues {
if len(v) == 0 {
break
}
sb.WriteByte('/')
sb.WriteString(string(v))
}

return sb.String()
}

// Equal returns true if the two keys are equal
func (k IndexDataStoreKey) Equal(other IndexDataStoreKey) bool {
if k.CollectionID != other.CollectionID {
return false
}
if k.IndexID != other.IndexID {
return false
}
if len(k.FieldValues) != len(other.FieldValues) {
return false
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
}
for i := range k.FieldValues {
if string(k.FieldValues[i]) != string(other.FieldValues[i]) {
return false
}
}
return true
}

func (k PrimaryDataStoreKey) ToDataStoreKey() DataStoreKey {
return DataStoreKey{
CollectionID: k.CollectionId,
Expand Down
Loading