Conversation
Signed-off-by: Carson Farmer <carson.farmer@gmail.com>
Signed-off-by: Carson Farmer <carson.farmer@gmail.com>
Signed-off-by: Carson Farmer <carson.farmer@gmail.com>
Signed-off-by: Carson Farmer <carson.farmer@gmail.com>
Signed-off-by: Carson Farmer <carson.farmer@gmail.com>
carsonfarmer
left a comment
There was a problem hiding this comment.
Some comments for when this thing is no longer WIP.
| previous, err := t.collection.db.datastore.Get(key) | ||
| if err == ds.ErrNotFound { | ||
| return nil, errCantSaveNonExistentInstance | ||
| a := core.Action{ |
There was a problem hiding this comment.
This means we can "Save" an instance even if we haven't seem it before, so long as it already has a valid _id.
There was a problem hiding this comment.
This is useful for reasons I've outlined elsewhere (see #440), but additionally, it circumvents a restriction on current Hub. With that in mind, we should think through the implications of this a little bit. Seems safe on the outset.
| parts := strings.Split(jt.Ref, "/") | ||
| if len(parts) < 1 { | ||
| return nil, ErrInvalidCollectionSchema | ||
| return make(map[string]*jsonschema.Type), nil |
There was a problem hiding this comment.
This should still be safe, because we always check and add the _id as needed. So even if the schema doesn't call for it, all ThreadDB instances will get an _id field. This makes it easier to support "empty" schemas.
| return newID, patchedValue | ||
| } | ||
|
|
||
| func getModifiedTag(t []byte) (int64, error) { |
There was a problem hiding this comment.
Just a copy-pasta pretty much from the _id equivalent.
|
|
||
| type Person struct { | ||
| ID core.InstanceID `json:"_id"` | ||
| Mod int64 `json:"_mod"` |
There was a problem hiding this comment.
Added for tests, this isn't needed by end-users. They can leave this off and things will work just fine.
| p := util.JSONFromInstance(Person{Name: "Alice", Age: 42}) | ||
| if err := m.Save(p); !errors.Is(err, errCantSaveNonExistentInstance) { | ||
| t.Fatal("shouldn't save non-existent instasnce") | ||
| if err := m.Save(p); err != nil { |
There was a problem hiding this comment.
Adjusting to allow save without prior instance.
| nameRx = regexp.MustCompile(`^[A-Za-z0-9]+(?:[-][A-Za-z0-9]+)*$`) | ||
| // register empty map in order to gob-encode old-format events with non-encodable time.Time which cbor decodes as map[string]interface{} | ||
| gob.Register(map[string]interface {}{}) | ||
| gob.Register(map[string]interface{}{}) |
| j1 = util.SetJSONProperty("Counter", -1, j1) | ||
| j1 = util.SetJSONProperty("Name", "Textile33", j1) | ||
| checkErr(t, c2.Save(j1)) | ||
| err = c2.Save(j1) |
There was a problem hiding this comment.
These are leftover from some earlier changes. I can revert them if we want.
|
|
||
| // Store returns the internal event store. | ||
| func (d *dispatcher) Store() datastore.Datastore { | ||
| func (d *dispatcher) Store() datastore.TxnDatastore { |
There was a problem hiding this comment.
Always has been a txndatastore in practice, now I want to make this explicit.
| return d.store | ||
| } | ||
|
|
||
| func (d *dispatcher) Lock() *sync.RWMutex { |
| key = dsDispatcherPrefix.ChildString(time). | ||
| ChildString(event.InstanceID().String()). | ||
| ChildString(event.Collection()) | ||
| ChildString(event.Collection()). |
There was a problem hiding this comment.
Up for discussion, but it defs makes queries for specific collection types within a db easier, and a lot more efficient.
There was a problem hiding this comment.
As far as I know, we aren't touching this data from anywhere else, so this shouldn't require a migration or check anywhere.
| } | ||
|
|
||
| // ModifiedSince returns a list of all instances that have been modified (and/or touched) since `time`. | ||
| func (c *Collection) ModifiedSince(time int64, opts ...TxnOption) (modified []core.InstanceID, err error) { |
There was a problem hiding this comment.
Note that I have not yet exposed this via any gRPC APIs/updates. Partly because I can work around having this for now using less efficient means, and partly because it should be reviewed before I bother exposing it.
There was a problem hiding this comment.
Would it make sense to combine this with Find, possibly as a new kind of query / condition? Then you could combine this functionality with other queries / conditions.
There was a problem hiding this comment.
Hmmm, interesting. That would be ideal (no new APIs). I'm not sure off the top of my head how this might work given we are querying the dispatcher directly... but as you suggest, some sort of additional query condition/operator might do the trick. Will look into this. In that case, I might mark that API as experimental, not expose it via gRPC for now, and explore this alternative in a follow on PR. Seem reasonable?
| previous, err := t.collection.db.datastore.Get(key) | ||
| if err == ds.ErrNotFound { | ||
| return nil, errCantSaveNonExistentInstance | ||
| a := core.Action{ |
There was a problem hiding this comment.
This is useful for reasons I've outlined elsewhere (see #440), but additionally, it circumvents a restriction on current Hub. With that in mind, we should think through the implications of this a little bit. Seems safe on the outset.
| key = dsDispatcherPrefix.ChildString(time). | ||
| ChildString(event.InstanceID().String()). | ||
| ChildString(event.Collection()) | ||
| ChildString(event.Collection()). |
| } | ||
|
|
||
| // ModifiedSince returns a list of all instances that have been modified (and/or touched) since `time`. | ||
| func (c *Collection) ModifiedSince(time int64, opts ...TxnOption) (modified []core.InstanceID, err error) { |
There was a problem hiding this comment.
Would it make sense to combine this with Find, possibly as a new kind of query / condition? Then you could combine this functionality with other queries / conditions.
This PR contains a few small nits and optimizations that make building on offline-first client for ThreadDB easier. The main changes are that a call to Save can now also lead to a Create action being generated if and only if the call to Save includes an existing Instance ID as a simple safety check. This allows callers to write or overwrite an existing Instance, without having to check first if it already exists. An additional change is that Delete now silently ignores missing Instances. This means a client can call delete on an Instance that might have already been deleted while it was offline or similar, without throwing an error.
Finally, the more important addition is the "special" _mod field. This is simple a "readonly" timestamp that gets added to all Instances. Any changes to this field will be ignored by the peer, and will be automatically updated on Save (and initialized on Create). This doesn't really have any effect on Delete, which is partly why the ModifiedSince experimental API was also created.