private async Task <OpResponse> ProcessReplace(RequestOp req) { OpResponse?opResponse = await Origin.ProcessRequest(req); await StoreInPreviousCaches(opResponse); return(opResponse !); }
public Task <OpResponse> GetResponse <M>(long id, int?freshnessSeconds = null) where M : class { var req = RequestOp.GetOp <M>( id, NowMs, freshnessSeconds ?? Config.DefaultFreshnessSeconds ); return(ProcessRequest(req)); }
public Task <OpResponse> QueryResponse <M>(string key, object criteria, int?freshnessSeconds = null) { var req = RequestOp.QueryOp <M>( key, criteria, NowMs, freshnessSeconds ?? Config.DefaultFreshnessSeconds ); return(ProcessRequest(req)); }
public async Task <OpResponse> Fetch(RequestOp requestOp) { if (requestOp.Type != typeof(Model)) { throw new Exception("requestOp.Type != typeof(Model)"); } switch (requestOp.Verb) { case RequestVerb.Get: var id = (IdType?)CascadeTypeUtils.ConvertTo(typeof(IdType), requestOp.Id); // ((IdType)requestOp.Id)!; if (id == null) { throw new Exception("Unable to get right value for Id"); } if (models.ContainsKey(id)) { return(new OpResponse( requestOp, Cascade.NowMs, connected: true, exists: true, result: models[id].Item1, arrivedAtMs: models[id].Item2 )); } else { return(new OpResponse( requestOp, Cascade.NowMs, connected: true, exists: false, result: null, arrivedAtMs: null )); } break; case RequestVerb.Query: if (collections.ContainsKey(requestOp.Key !)) { return(new OpResponse( requestOp, Cascade.NowMs, connected: true, exists: true, result: collections[requestOp.Key !].Item1,
public OpResponse( RequestOp requestOp, long timeMs, bool connected, bool exists, long?arrivedAtMs, object?result ) { RequestOp = requestOp; TimeMs = timeMs; Connected = connected; Exists = exists; ArrivedAtMs = arrivedAtMs; Result = result; }
private Task <OpResponse> ProcessRequest(RequestOp req) { switch (req.Verb) { case RequestVerb.Get: case RequestVerb.Query: return(ProcessReadOrQuery(req)); case RequestVerb.Create: return(ProcessCreate(req)); case RequestVerb.Replace: return(ProcessReplace(req)); default: throw new ArgumentException("Unsupported verb"); } }
public async Task TestCollections() { var path = System.IO.Path.GetTempFileName(); var conn = new SQLite.SQLiteAsyncConnection(path); var db = new TestDatabase(conn); await db.Reset(); var sqliteThingCache = new SqliteClassCache <Parent, long>(db); await sqliteThingCache.Setup(); var sqliteGadgetCache = new SqliteClassCache <Child, string>(db); await sqliteGadgetCache.Setup(); var cache = new ModelCache(aClassCache: new Dictionary <Type, IModelClassCache>() { { typeof(Parent), sqliteThingCache }, { typeof(Child), sqliteGadgetCache } }); var cascade = new CascadeDataLayer(origin, new ICascadeCache[] { cache }, new CascadeConfig() { DefaultFreshnessSeconds = 1 }); var collection_key = "my_things"; var ids = ImmutableArray.Create <object>(1, 2, 3); var response = await cache.Fetch(RequestOp.QueryOp <Parent>(collection_key, new JsonObject(), 0)); Assert.AreEqual(false, response.Exists); Assert.AreEqual(null, response.Result); await cache.StoreCollection(typeof(Parent), collection_key, ids, 0); response = await cache.Fetch(RequestOp.QueryOp <Parent>(collection_key, null, 0)); Assert.IsTrue(CascadeTypeUtils.IsEqualEnumerable(ids, response.ResultIds)); response = await cache.Fetch(RequestOp.QueryOp <Parent>("not_my_key", null, 0)); Assert.IsFalse(response.Exists); response = await cache.Fetch(RequestOp.QueryOp <Child>(collection_key, null, 0)); Assert.IsFalse(response.Exists); }
private async Task <OpResponse> ProcessReadOrQuery(RequestOp requestOp) { object? value; ICascadeCache?layerFound = null; OpResponse? opResponse = null; // try each cache layer foreach (var layer in CacheLayers) { var res = await layer.Fetch(requestOp); if (res.PresentAndFresh()) { layerFound = layer; opResponse = res; break; } } // if still not found, try origin if (opResponse == null) { opResponse = await Origin.ProcessRequest(requestOp); } if (requestOp.Verb == RequestVerb.Query && opResponse.IsIdResults) { var modelResponses = await GetModelsForIds(requestOp.Type, requestOp.FreshnessSeconds ?? Config.DefaultFreshnessSeconds, opResponse.ResultIds); // don't need to do this because the get above will achieve the same // foreach (var modelResponse in modelResponses) { // await StoreInPreviousCaches(opResponse, layerFound); // } await StoreInPreviousCaches(opResponse, layerFound); // just store ResultIds opResponse = opResponse.withChanges(result: modelResponses.Select(r => r.Result).ToImmutableArray()); // modify the response with models instead of ids } else { await StoreInPreviousCaches(opResponse, layerFound); } return(opResponse !); }
// // // // special case for enums // if (targetType.IsEnum) { // // we could be going from an int -> enum so specifically let // // the Enum object take care of this conversion // if (value != null) { // value = Enum.ToObject(targetType, value); // } // } // else { // // returns an System.Object with the specified System.Type and whose value is // // equivalent to the specified object. // value = Convert.ChangeType(value, targetType); // } // set the value of the property // propertyInfo.SetValue(target, value, null); // } private async Task processHasMany(SuperModel model, Type modelType, PropertyInfo propertyInfo, HasManyAttribute attribute) { var propertyType = CascadeTypeUtils.DeNullType(propertyInfo.PropertyType); var isEnumerable = (propertyType?.Implements <IEnumerable>() ?? false) && propertyType != typeof(string); var foreignType = isEnumerable ? CascadeTypeUtils.InnerType(propertyType !) : null; foreignType = foreignType != null?CascadeTypeUtils.DeNullType(foreignType) : null; if (foreignType == null) { throw new ArgumentException("Unable to get foreign model type. Property should be of type ImmutableArray<ChildModel>"); } // var typeLayers = GetTypeLayers(propertyType); // var nonNullableType = DeNullType(propertyType); // // typeLayers.FirstOrDefault(t => t.Name != "Nullable`1"); // // var foreignType = isEnumerable ? propertyType.GetGenericArguments().FirstOrDefault() : null; object modelId = CascadeTypeUtils.GetCascadeId(model); var key = $"HasMany__{foreignType.Name}__{attribute.ForeignIdProperty}__{modelId}"; var requestOp = new RequestOp( NowMs, foreignType, RequestVerb.Query, null, null, 0, new Dictionary <string, object>() { [attribute.ForeignIdProperty] = modelId }, key ); var opResponse = await ProcessRequest(requestOp); CascadeTypeUtils.SetModelCollectionProperty(model, propertyInfo, opResponse.Results); //propertyInfo.SetValue(model,opResponse.Results); }
private async Task processBelongsTo(object model, Type modelType, PropertyInfo propertyInfo, BelongsToAttribute attribute) { var foreignModelType = CascadeTypeUtils.DeNullType(propertyInfo.PropertyType); var idProperty = modelType.GetProperty(attribute.IdProperty); var id = idProperty.GetValue(model); if (id == null) { return; } var requestOp = new RequestOp( NowMs, foreignModelType, RequestVerb.Get, id, freshnessSeconds: Config.DefaultFreshnessSeconds ); var opResponse = await ProcessRequest(requestOp); SetModelProperty(model, propertyInfo, opResponse.Result); }
public async Task <OpResponse> Fetch(RequestOp requestOp) { if (requestOp.Type != typeof(Model)) { throw new Exception("requestOp.Type != typeof(Model)"); } switch (requestOp.Verb) { case RequestVerb.Get: var id = (IdType?)CascadeTypeUtils.ConvertTo(typeof(IdType), requestOp.Id); // ((IdType)requestOp.Id)!; if (id == null) { throw new Exception("Unable to get right value for Id"); } if (await _database.Exists <Model>(id)) { var meta = await _database.Get <CascadeModelMeta>(CascadeModelMeta.GenerateId <Model>(id)); return(new OpResponse( requestOp, Cascade !.NowMs, connected: true, exists: true, result: await _database.Get <Model>(id), arrivedAtMs: meta?.arrived_at )); } else { return(new OpResponse( requestOp, Cascade !.NowMs, connected: true, exists: false, result: null, arrivedAtMs: null )); } break; case RequestVerb.Query: var collection = await _database.Get <CascadeCollection>(CascadeCollection.GenerateId <Model>(requestOp.Key !)); if (collection != null) { return(new OpResponse( requestOp, Cascade !.NowMs, connected: true, exists: true, result: collection.objectIds, arrivedAtMs: collection.arrived_at )); } else { return(new OpResponse( requestOp, Cascade !.NowMs, connected: true, exists: false, result: null, arrivedAtMs: null )); } break; default: throw new NotImplementedException($"Unsupported {requestOp.Verb}"); } }
public async Task ReadWithModelCachesMultitest() { var thingModelStore1 = new ModelClassCache <Parent, long>(); var cache1 = new ModelCache(aClassCache: new Dictionary <Type, IModelClassCache>() { { typeof(Parent), thingModelStore1 } }); var thingModelStore2 = new ModelClassCache <Parent, long>(); var cache2 = new ModelCache(aClassCache: new Dictionary <Type, IModelClassCache>() { { typeof(Parent), thingModelStore2 } }); // read from origin var cascade = new CascadeDataLayer(origin, new ICascadeCache[] { cache1, cache2 }, new CascadeConfig() { DefaultFreshnessSeconds = 1 }); var thing1 = await cascade.Get <Parent>(5); Assert.AreEqual(5, thing1 !.id); Assert.AreEqual(cascade.NowMs, thing1.updatedAtMs); // should also be in both caches var store1ThingResponse = await thingModelStore1.Fetch(RequestOp.GetOp <Parent>(5, cascade.NowMs)); Assert.AreEqual((store1ThingResponse.Result as Parent) !.id, 5); Assert.AreEqual(cascade.NowMs, (store1ThingResponse.Result as Parent) !.updatedAtMs); var store2ThingResponse = await thingModelStore2.Fetch(RequestOp.GetOp <Parent>(5, cascade.NowMs)); Assert.AreEqual((store2ThingResponse.Result as Parent) !.id, 5); Assert.AreEqual(cascade.NowMs, (store2ThingResponse.Result as Parent) !.updatedAtMs); origin.IncNowMs(); // freshness=5 allows for cached version var thing2 = (await cascade.Get <Parent>(5, freshnessSeconds: 5)) !; Assert.AreEqual(thing1.updatedAtMs, thing2.updatedAtMs); // freshness=0 doesn't allow for cached version var thing3 = (await cascade.Get <Parent>(5, freshnessSeconds: 0)) !; Assert.AreEqual(origin.NowMs, thing3.updatedAtMs); // caches should also be updated store1ThingResponse = await thingModelStore1.Fetch(RequestOp.GetOp <Parent>(5, cascade.NowMs)); Assert.AreEqual(origin.NowMs, (store1ThingResponse.Result as Parent) !.updatedAtMs); store2ThingResponse = await thingModelStore2.Fetch(RequestOp.GetOp <Parent>(5, cascade.NowMs)); Assert.AreEqual(origin.NowMs, (store2ThingResponse.Result as Parent) !.updatedAtMs); origin.IncNowMs(2000); // freshness=2 should allow for cached version var thing4 = (await cascade.Get <Parent>(5, freshnessSeconds: 2)) !; Assert.AreEqual(thing3.updatedAtMs, thing4.updatedAtMs); // freshness=1 should get fresh version var thing5 = (await cascade.Get <Parent>(5, freshnessSeconds: 1)) !; Assert.AreEqual(origin.NowMs, thing5.updatedAtMs); origin.IncNowMs(1000); // clear cache1, freshnessSeconds=1 should return value from cache2 and update cache1 await cache1.Clear(); var thing6 = (await cascade.Get <Parent>(thing4.id, freshnessSeconds: 1)) !; // should get cache2 version Assert.AreEqual(thing6.updatedAtMs, thing5.updatedAtMs); store1ThingResponse = await thingModelStore1.Fetch(RequestOp.GetOp <Parent>(5, cascade.NowMs)); Assert.AreEqual(thing6.updatedAtMs, (store1ThingResponse.Result as Parent) !.updatedAtMs); }
public async Task TestMetadata() { var path = System.IO.Path.GetTempFileName(); var conn = new SQLite.SQLiteAsyncConnection(path); var db = new TestDatabase(conn); await db.Reset(); var sqliteThingCache = new SqliteClassCache <Parent, long>(db); await sqliteThingCache.Setup(); var sqliteGadgetCache = new SqliteClassCache <Child, string>(db); await sqliteGadgetCache.Setup(); var cache1 = new ModelCache(aClassCache: new Dictionary <Type, IModelClassCache>() { { typeof(Parent), sqliteThingCache }, { typeof(Child), sqliteGadgetCache } }); // read from origin var cascade = new CascadeDataLayer(origin, new ICascadeCache[] { cache1 }, new CascadeConfig() { DefaultFreshnessSeconds = 1 }); var thing5 = new Parent() { id = 5, colour = "red" }; var thing5ArrivedAt = cascade.NowMs; await sqliteThingCache.Store(thing5.id, thing5, thing5ArrivedAt); origin.IncNowMs(); var gadget6 = new Child() { id = "abc", weight = 2.5, power = 9.2 }; var gadget6ArrivedAt = cascade.NowMs; await sqliteGadgetCache.Store(gadget6.id, gadget6, gadget6ArrivedAt); origin.IncNowMs(); var opResponse = await sqliteThingCache.Fetch(RequestOp.GetOp <Parent>(thing5.id, cascade.NowMs)); var loaded5 = (opResponse.Result as Parent) !; Assert.AreEqual(thing5ArrivedAt, opResponse.ArrivedAtMs); Assert.AreEqual(thing5.colour, loaded5.colour); opResponse = await sqliteGadgetCache.Fetch(RequestOp.GetOp <Child>(gadget6.id, cascade.NowMs)); var loaded6 = (opResponse.Result as Child) !; Assert.AreEqual(gadget6ArrivedAt, opResponse.ArrivedAtMs); Assert.AreEqual(gadget6.weight, loaded6.weight); await sqliteGadgetCache.Clear(); // thing unaffected opResponse = await sqliteThingCache.Fetch(RequestOp.GetOp <Parent>(thing5.id, cascade.NowMs)); Assert.NotNull(opResponse.Result); Assert.AreEqual(thing5ArrivedAt, opResponse.ArrivedAtMs); // gadget cleared including metadata opResponse = await sqliteGadgetCache.Fetch(RequestOp.GetOp <Child>(gadget6.id, cascade.NowMs)); Assert.IsNull(opResponse.Result); Assert.IsNull(opResponse.ArrivedAtMs); var meta6 = await db.Get <CascadeModelMeta>(CascadeModelMeta.GenerateId <Child>(6)); Assert.IsNull(meta6); }