-
Notifications
You must be signed in to change notification settings - Fork 51
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
refactor: Make CollectionDescription.Name Option #2223
Changes from all commits
579bdf4
371a722
b2703af
74fb8a6
08f0745
3610f78
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,8 @@ package client | |
import ( | ||
"context" | ||
|
||
"github.com/sourcenetwork/immutable" | ||
|
||
"github.com/sourcenetwork/defradb/datastore" | ||
) | ||
|
||
|
@@ -32,7 +34,7 @@ type CollectionDefinition struct { | |
// Many functions on this object will interact with the underlying datastores. | ||
type Collection interface { | ||
// Name returns the name of this collection. | ||
Name() string | ||
Name() immutable.Option[string] | ||
|
||
// ID returns the ID of this Collection. | ||
ID() uint32 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Will There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ID will always be there. |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,8 +11,11 @@ | |
package client | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/sourcenetwork/immutable" | ||
|
||
"github.com/sourcenetwork/defradb/client/request" | ||
) | ||
|
||
|
@@ -22,7 +25,7 @@ | |
// | ||
// It is conceptually local to the node hosting the DefraDB instance, but currently there | ||
// is no means to update the local value so that it differs from the (global) schema name. | ||
Name string | ||
Name immutable.Option[string] | ||
|
||
// ID is the local identifier of this collection. | ||
// | ||
|
@@ -32,12 +35,11 @@ | |
// The ID of the schema version that this collection is at. | ||
SchemaVersionID string | ||
|
||
// BaseQuery contains the base query of this view, if this collection is a view. | ||
// Sources is the set of sources from which this collection draws data. | ||
// | ||
// The query will be saved, and then may be accessed by other actors on demand. Actor defined | ||
// aggregates, filters and other logic (such as LensVM transforms) will execute on top of this | ||
// base query before the result is returned to the actor. | ||
BaseQuery *request.Select | ||
// Currently supported source types are: | ||
// - [QuerySource] | ||
Sources []any | ||
|
||
// Indexes contains the secondary indexes that this Collection has. | ||
Indexes []IndexDescription | ||
|
@@ -78,13 +80,37 @@ | |
schema *SchemaDescription, | ||
) (FieldDescription, bool) { | ||
for _, field := range schema.Fields { | ||
if field.RelationName == relationName && !(col.Name == otherCollectionName && otherFieldName == field.Name) { | ||
if field.RelationName == relationName && !(col.Name.Value() == otherCollectionName && otherFieldName == field.Name) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
question: Same as above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar answer to the above in that |
||
return field, true | ||
} | ||
} | ||
return FieldDescription{}, false | ||
} | ||
|
||
// QuerySources returns all the Sources of type [QuerySource] | ||
func (col CollectionDescription) QuerySources() []*QuerySource { | ||
return sourcesOfType[*QuerySource](col) | ||
} | ||
|
||
func sourcesOfType[ResultType any](col CollectionDescription) []ResultType { | ||
result := []ResultType{} | ||
for _, source := range col.Sources { | ||
if typedSource, isOfType := source.(ResultType); isOfType { | ||
result = append(result, typedSource) | ||
} | ||
} | ||
return result | ||
} | ||
|
||
// QuerySource represents a collection data source from a query. | ||
// | ||
// The query will be executed when data from this source is requested, and the query results | ||
// yielded to the consumer. | ||
type QuerySource struct { | ||
// Query contains the base query of this data source. | ||
Query request.Select | ||
} | ||
|
||
// SchemaDescription describes a Schema and its associated metadata. | ||
type SchemaDescription struct { | ||
// Root is the version agnostic identifier for this schema. | ||
|
@@ -322,3 +348,59 @@ | |
func (m RelationType) IsSet(target RelationType) bool { | ||
return m&target > 0 | ||
} | ||
|
||
// collectionDescription is a private type used to facilitate the unmarshalling | ||
// of json to a [CollectionDescription]. | ||
type collectionDescription struct { | ||
// These properties are unmarshalled using the default json unmarshaller | ||
Name immutable.Option[string] | ||
ID uint32 | ||
SchemaVersionID string | ||
Indexes []IndexDescription | ||
|
||
// Properties below this line are unmarshalled using custom logic in [UnmarshalJSON] | ||
Sources []map[string]json.RawMessage | ||
} | ||
|
||
func (c *CollectionDescription) UnmarshalJSON(bytes []byte) error { | ||
var descMap collectionDescription | ||
err := json.Unmarshal(bytes, &descMap) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
c.Name = descMap.Name | ||
c.ID = descMap.ID | ||
c.SchemaVersionID = descMap.SchemaVersionID | ||
c.Indexes = descMap.Indexes | ||
c.Sources = make([]any, len(descMap.Sources)) | ||
|
||
for i, source := range descMap.Sources { | ||
sourceJson, err := json.Marshal(source) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var sourceValue any | ||
// We detect which concrete type each `Source` object is by detecting | ||
// non-nillable fields, if the key is present it must be of that type. | ||
// They must be non-nillable as nil values may have their keys omitted from | ||
// the json. This also relies on the fields being unique. We may wish to change | ||
// this later to custom-serialize with a `_type` property. | ||
if _, ok := source["Query"]; ok { | ||
// This must be a QuerySource, as only the `QuerySource` type has a `Query` field | ||
var querySource QuerySource | ||
err := json.Unmarshal(sourceJson, &querySource) | ||
if err != nil { | ||
return err | ||
} | ||
sourceValue = &querySource | ||
} else { | ||
return ErrFailedToUnmarshalCollection | ||
} | ||
|
||
c.Sources[i] = sourceValue | ||
} | ||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,34 +17,38 @@ import ( | |
) | ||
|
||
const ( | ||
errFieldNotExist string = "The given field does not exist" | ||
errUnexpectedType string = "unexpected type" | ||
errParsingFailed string = "failed to parse argument" | ||
errUninitializeProperty string = "invalid state, required property is uninitialized" | ||
errMaxTxnRetries string = "reached maximum transaction reties" | ||
errRelationOneSided string = "relation must be defined on both schemas" | ||
errCollectionNotFound string = "collection not found" | ||
errFieldOrAliasToFieldNotExist string = "The given field or alias to field does not exist" | ||
errUnknownCRDT string = "unknown crdt" | ||
errCRDTKindMismatch string = "CRDT type %s can't be assigned to field kind %s" | ||
errInvalidCRDTType string = "CRDT type not supported" | ||
errFieldNotExist string = "The given field does not exist" | ||
errUnexpectedType string = "unexpected type" | ||
errParsingFailed string = "failed to parse argument" | ||
errUninitializeProperty string = "invalid state, required property is uninitialized" | ||
errMaxTxnRetries string = "reached maximum transaction reties" | ||
errRelationOneSided string = "relation must be defined on both schemas" | ||
errCollectionNotFound string = "collection not found" | ||
errFieldOrAliasToFieldNotExist string = "The given field or alias to field does not exist" | ||
errUnknownCRDT string = "unknown crdt" | ||
errCRDTKindMismatch string = "CRDT type %s can't be assigned to field kind %s" | ||
errInvalidCRDTType string = "CRDT type not supported" | ||
errFailedToUnmarshalCollection string = "failed to unmarshal collection json" | ||
errOperationNotPermittedOnNamelessCols string = "operation not permitted on nameless collection" | ||
) | ||
|
||
// Errors returnable from this package. | ||
// | ||
// This list is incomplete and undefined errors may also be returned. | ||
// Errors returned from this package may be tested against these errors with errors.Is. | ||
var ( | ||
ErrFieldNotExist = errors.New(errFieldNotExist) | ||
ErrUnexpectedType = errors.New(errUnexpectedType) | ||
ErrFieldNotObject = errors.New("trying to access field on a non object type") | ||
ErrValueTypeMismatch = errors.New("value does not match indicated type") | ||
ErrDocumentNotFound = errors.New("no document for the given ID exists") | ||
ErrInvalidUpdateTarget = errors.New("the target document to update is of invalid type") | ||
ErrInvalidUpdater = errors.New("the updater of a document is of invalid type") | ||
ErrInvalidDeleteTarget = errors.New("the target document to delete is of invalid type") | ||
ErrMalformedDocID = errors.New("malformed document ID, missing either version or cid") | ||
ErrInvalidDocIDVersion = errors.New("invalid document ID version") | ||
ErrFieldNotExist = errors.New(errFieldNotExist) | ||
ErrUnexpectedType = errors.New(errUnexpectedType) | ||
ErrFailedToUnmarshalCollection = errors.New(errFailedToUnmarshalCollection) | ||
ErrOperationNotPermittedOnNamelessCols = errors.New(errOperationNotPermittedOnNamelessCols) | ||
ErrFieldNotObject = errors.New("trying to access field on a non object type") | ||
ErrValueTypeMismatch = errors.New("value does not match indicated type") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: would be nice to just remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
ErrDocumentNotFound = errors.New("no document for the given ID exists") | ||
ErrInvalidUpdateTarget = errors.New("the target document to update is of invalid type") | ||
ErrInvalidUpdater = errors.New("the updater of a document is of invalid type") | ||
ErrInvalidDeleteTarget = errors.New("the target document to delete is of invalid type") | ||
ErrMalformedDocID = errors.New("malformed document ID, missing either version or cid") | ||
ErrInvalidDocIDVersion = errors.New("invalid document ID version") | ||
) | ||
|
||
// NewErrFieldNotExist returns an error indicating that the given field does not exist. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
|
||
"github.com/ipfs/go-cid" | ||
ds "github.com/ipfs/go-datastore" | ||
"github.com/sourcenetwork/immutable" | ||
|
||
"github.com/sourcenetwork/defradb/client" | ||
"github.com/sourcenetwork/defradb/errors" | ||
|
@@ -132,8 +133,8 @@ | |
|
||
// CollectionIndexKey to a stored description of an index | ||
type CollectionIndexKey struct { | ||
// CollectionName is the name of the collection that the index is on | ||
CollectionName string | ||
// CollectionID is the id of the collection that the index is on | ||
CollectionID immutable.Option[uint32] | ||
// IndexName is the name of the index | ||
IndexName string | ||
} | ||
|
@@ -291,22 +292,28 @@ | |
} | ||
|
||
// NewCollectionIndexKey creates a new CollectionIndexKey from a collection name and index name. | ||
func NewCollectionIndexKey(colID, indexName string) CollectionIndexKey { | ||
return CollectionIndexKey{CollectionName: colID, IndexName: indexName} | ||
func NewCollectionIndexKey(colID immutable.Option[uint32], indexName string) CollectionIndexKey { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Related to my question above, since ID is optional (due to I guess name being optional) then should probably change on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you misunderstand, if |
||
return CollectionIndexKey{CollectionID: 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] | ||
// /collection/index/[CollectionID]/[IndexName] | ||
// | ||
// Where [IndexName] might be omitted. Anything else will return an error. | ||
func NewCollectionIndexKeyFromString(key string) (CollectionIndexKey, error) { | ||
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]} | ||
|
||
colID, err := strconv.Atoi(keyArr[3]) | ||
if err != nil { | ||
return CollectionIndexKey{}, err | ||
} | ||
|
||
result := CollectionIndexKey{CollectionID: immutable.Some(uint32(colID))} | ||
if len(keyArr) == 5 { | ||
result.IndexName = keyArr[4] | ||
} | ||
|
@@ -315,13 +322,13 @@ | |
|
||
// 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 | ||
// /collection/index/[CollectionID]/[IndexName] | ||
// if [CollectionID] is empty, the rest is ignored | ||
func (k CollectionIndexKey) ToString() string { | ||
result := COLLECTION_INDEX | ||
|
||
if k.CollectionName != "" { | ||
result = result + "/" + k.CollectionName | ||
if k.CollectionID.HasValue() { | ||
result = result + "/" + fmt.Sprint(k.CollectionID.Value()) | ||
if k.IndexName != "" { | ||
result = result + "/" + k.IndexName | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -137,7 +137,7 @@ func (db *db) basicExport(ctx context.Context, txn datastore.Txn, config *client | |
} | ||
colNameCache := map[string]struct{}{} | ||
for _, col := range cols { | ||
colNameCache[col.Name()] = struct{}{} | ||
colNameCache[col.Name().Value()] = struct{}{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
question: Same as above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Name can't be empty here. The code is exporting queryable (i.e. named) data. |
||
} | ||
|
||
tempFile := config.Filepath + ".temp" | ||
|
@@ -181,8 +181,8 @@ func (db *db) basicExport(ctx context.Context, txn datastore.Txn, config *client | |
// set collection | ||
err = writeString( | ||
f, | ||
fmt.Sprintf("\"%s\":[", col.Name()), | ||
fmt.Sprintf(" \"%s\": [\n", col.Name()), | ||
fmt.Sprintf("\"%s\":[", col.Name().Value()), | ||
fmt.Sprintf(" \"%s\": [\n", col.Name().Value()), | ||
Comment on lines
+184
to
+185
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
question: Same as above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Name can't be empty here. The code is exporting queryable (i.e. named) data. |
||
config.Pretty, | ||
) | ||
if err != nil { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Why have you skipped
HasValue()
here before callingValue()
. If IRRC the value returned is invalid unlessHasValue() == true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name
cannot be""
here, and at the momentfetchedCols
cannot contain any nameless collections.The code that populates
fetchedCols
will likely need restructuring somewhat in the followup PR that sorts out the version stuff, this line will be looked at again at that point.