From 0c31aa37584150fa6d658e01d710eb6d29b74c78 Mon Sep 17 00:00:00 2001 From: Shrirang Shirodkar Date: Thu, 11 Jun 2015 13:46:38 -0700 Subject: [PATCH] [iOS] Add support for configuring page size of a pull operation --- .../project.pbxproj | 8 ++ sdk/iOS/src/MSPullSettings.h | 29 ++++ sdk/iOS/src/MSPullSettings.m | 44 ++++++ sdk/iOS/src/MSSyncContext.m | 18 ++- sdk/iOS/src/MSSyncContextInternal.h | 3 + sdk/iOS/src/MSSyncTable.h | 10 +- sdk/iOS/src/MSSyncTable.m | 7 +- sdk/iOS/test/MSSyncTableTests.m | 136 ++++++++++++++++++ 8 files changed, 248 insertions(+), 7 deletions(-) create mode 100644 sdk/iOS/src/MSPullSettings.h create mode 100644 sdk/iOS/src/MSPullSettings.m diff --git a/sdk/iOS/WindowsAzureMobileServices.xcodeproj/project.pbxproj b/sdk/iOS/WindowsAzureMobileServices.xcodeproj/project.pbxproj index 411c4a711..bcf684c08 100644 --- a/sdk/iOS/WindowsAzureMobileServices.xcodeproj/project.pbxproj +++ b/sdk/iOS/WindowsAzureMobileServices.xcodeproj/project.pbxproj @@ -81,6 +81,8 @@ CF762EC91A1D37A40018C292 /* MSDateOffset.m in Sources */ = {isa = PBXBuildFile; fileRef = CF762EC71A1D36950018C292 /* MSDateOffset.m */; }; CFAE6E0A1A3B84D700734128 /* MSTableConfigValue.m in Sources */ = {isa = PBXBuildFile; fileRef = CFAE6E091A3B84D700734128 /* MSTableConfigValue.m */; }; CFAE6E0C1A3B84E400734128 /* MSTableConfigValue.h in Headers */ = {isa = PBXBuildFile; fileRef = CFAE6E0B1A3B84E400734128 /* MSTableConfigValue.h */; }; + D0D3B9361B290D80002A126A /* MSPullSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D3B9351B290D80002A126A /* MSPullSettings.m */; }; + D0D3B9371B2A267F002A126A /* MSPullSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = D0D3B9341B2906EF002A126A /* MSPullSettings.h */; settings = {ATTRIBUTES = (Public, ); }; }; E8146642165BE0150038EBE5 /* MSLogin.h in Headers */ = {isa = PBXBuildFile; fileRef = E8146640165BE0150038EBE5 /* MSLogin.h */; }; E8146643165BE0150038EBE5 /* MSLogin.m in Sources */ = {isa = PBXBuildFile; fileRef = E8146641165BE0150038EBE5 /* MSLogin.m */; }; E8146646165BE2440038EBE5 /* MSLoginSerializer.h in Headers */ = {isa = PBXBuildFile; fileRef = E8146644165BE2440038EBE5 /* MSLoginSerializer.h */; }; @@ -241,6 +243,8 @@ CF762EC71A1D36950018C292 /* MSDateOffset.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSDateOffset.m; sourceTree = ""; }; CFAE6E091A3B84D700734128 /* MSTableConfigValue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSTableConfigValue.m; sourceTree = ""; }; CFAE6E0B1A3B84E400734128 /* MSTableConfigValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSTableConfigValue.h; sourceTree = ""; }; + D0D3B9341B2906EF002A126A /* MSPullSettings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSPullSettings.h; sourceTree = ""; }; + D0D3B9351B290D80002A126A /* MSPullSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSPullSettings.m; sourceTree = ""; }; E8146640165BE0150038EBE5 /* MSLogin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSLogin.h; sourceTree = ""; }; E8146641165BE0150038EBE5 /* MSLogin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSLogin.m; sourceTree = ""; }; E8146644165BE2440038EBE5 /* MSLoginSerializer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSLoginSerializer.h; sourceTree = ""; }; @@ -359,6 +363,8 @@ isa = PBXGroup; children = ( A2ACD539192322760012B1ED /* CoreData */, + D0D3B9341B2906EF002A126A /* MSPullSettings.h */, + D0D3B9351B290D80002A126A /* MSPullSettings.m */, A2C14C6819105D9F00A58609 /* MSSyncContextReadResult.h */, A2C14C6919105D9F00A58609 /* MSSyncContextReadResult.m */, A258890718A19D0B00962F9A /* MSSyncTable.h */, @@ -667,6 +673,7 @@ A258891618A19D0B00962F9A /* MSSyncTable.h in Headers */, E8E37912161F769D00C13F00 /* MSUser.h in Headers */, A258891418A19D0B00962F9A /* MSSyncContext.h in Headers */, + D0D3B9371B2A267F002A126A /* MSPullSettings.h in Headers */, A281900418BB2FF5001B14E7 /* MSTableOperationError.h in Headers */, E8E37913161F769D00C13F00 /* MSFilter.h in Headers */, A2ACD540192322C50012B1ED /* MSCoreDataStore.h in Headers */, @@ -844,6 +851,7 @@ CF762EC91A1D37A40018C292 /* MSDateOffset.m in Sources */, E8F33B15161667CE002DD7C6 /* MSClient.m in Sources */, A258890C18A19D0B00962F9A /* MSOperationQueue.m in Sources */, + D0D3B9361B290D80002A126A /* MSPullSettings.m in Sources */, E8F33B16161667CE002DD7C6 /* MSError.m in Sources */, 7929542919B7C793006A3829 /* MSQueryResult.m in Sources */, E8F33B17161667CE002DD7C6 /* MSQuery.m in Sources */, diff --git a/sdk/iOS/src/MSPullSettings.h b/sdk/iOS/src/MSPullSettings.h new file mode 100644 index 000000000..152987b9a --- /dev/null +++ b/sdk/iOS/src/MSPullSettings.h @@ -0,0 +1,29 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// ---------------------------------------------------------------------------- + +#import + +/// Settings that control the pull behavior +@interface MSPullSettings : NSObject + +#pragma mark * Properties + +///@name Properties that control pull behavior +///@{ + +/// Size of pages requested from the server while performing a pull +@property (nonatomic) NSInteger pageSize; + +///@} + +#pragma mark * Class methods + +///@name Class factory methods + +/// Class factory method for getting default pull settings ++ (MSPullSettings *)default; + +///} + +@end diff --git a/sdk/iOS/src/MSPullSettings.m b/sdk/iOS/src/MSPullSettings.m new file mode 100644 index 000000000..62ab03c2b --- /dev/null +++ b/sdk/iOS/src/MSPullSettings.m @@ -0,0 +1,44 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// ---------------------------------------------------------------------------- + +#import "MSPullSettings.h" + +#pragma mark * MSPullSettings Implementation + +@implementation MSPullSettings + +NSInteger const defaultPageSize = 50; + +@synthesize pageSize = _pageSize; + +#pragma mark * Initializer method(s) + +- (id)init { + self = [super init]; + if (self) { + _pageSize = defaultPageSize; + } + + return self; +} + +#pragma mark * Accessor method(s) + +- (void)setPageSize:(NSInteger)pageSize { + if (pageSize <= 0) { + _pageSize = defaultPageSize; + } + else { + _pageSize = pageSize; + } +} + +#pragma mark * Class factory method(s) + ++ (MSPullSettings *)default { + return [MSPullSettings new]; +} + +@end + diff --git a/sdk/iOS/src/MSSyncContext.m b/sdk/iOS/src/MSSyncContext.m index ca294d3b8..a3c418d19 100644 --- a/sdk/iOS/src/MSSyncContext.m +++ b/sdk/iOS/src/MSSyncContext.m @@ -21,8 +21,6 @@ @implementation MSSyncContext { dispatch_queue_t writeOperationQueue; } -NSInteger const defaultFetchLimit = 50; - static NSOperationQueue *pushQueue_; @synthesize delegate = delegate_; @@ -322,12 +320,22 @@ - (void) cancelOperation:(MSTableOperation *)operation discardItemWithCompletion }); } -/// Verify our input is valid and try to pull our data down from the server +/// Pull data down from the server - (NSOperation *) pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(MSSyncBlock)completion; +{ + return [self pullWithQuery:query queryId:queryId pullSettings:nil completion:completion]; +} + +/// Verify our input is valid and try to pull our data down from the server +- (NSOperation *) pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId pullSettings:(MSPullSettings *)pullSettings completion:(MSSyncBlock)completion; { // make a copy since we'll be modifying it internally MSQuery *queryCopy = [query copy]; + if (!pullSettings) { + pullSettings = [MSPullSettings default]; + } + // We want to throw on unsupported fields so we can change this decision later NSError *error; NSDictionary *isDeletedParams = [MSSyncContext dictionary:queryCopy.parameters entriesForCaseInsensitiveKey:@"__includedeleted"]; @@ -401,9 +409,9 @@ - (NSOperation *) pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId com } // For a Pull we treat fetchLimit as the total records we should pull by paging. If there is no fetchLimit, we pull everything. - // We enforce a page size of 50. + // We enforce a page size of |pullSettings.pageSize| NSInteger maxRecords = query.fetchLimit >= 0 ? query.fetchLimit : NSIntegerMax; - queryCopy.fetchLimit = MIN(maxRecords, defaultFetchLimit); + queryCopy.fetchLimit = MIN(maxRecords, pullSettings.pageSize); // Begin the actual pull request return [self pullWithQueryInternal:queryCopy queryId:queryId maxRecords:maxRecords completion:completion]; diff --git a/sdk/iOS/src/MSSyncContextInternal.h b/sdk/iOS/src/MSSyncContextInternal.h index 071044554..19569a6d9 100644 --- a/sdk/iOS/src/MSSyncContextInternal.h +++ b/sdk/iOS/src/MSSyncContextInternal.h @@ -6,6 +6,7 @@ #import "MSClient.h" #import "MSOperationQueue.h" #import "MSTable.h" +#import "MSPullSettings.h" @interface MSSyncContext() @@ -27,6 +28,8 @@ -(NSOperation *) pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(MSSyncBlock)completion; +-(NSOperation *) pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId pullSettings:(MSPullSettings *)pullSettings completion:(MSSyncBlock)completion; + -(NSOperation *) purgeWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion; -(NSOperation *) forcePurgeWithTable:(MSSyncTable *)syncTable completion:(MSSyncBlock)completion; diff --git a/sdk/iOS/src/MSSyncTable.h b/sdk/iOS/src/MSSyncTable.h index 8d11a7cb2..2d18c67ad 100644 --- a/sdk/iOS/src/MSSyncTable.h +++ b/sdk/iOS/src/MSSyncTable.h @@ -5,6 +5,7 @@ #import #import "MSClient.h" #import "MSTable.h" +#import "MSPullSettings.h" @class MSQueuePullOperation; @class MSQueuePurgeOperation; @@ -96,12 +97,19 @@ /// @{ /// Initiates a request to go to the server and get a set of records matching the specified -/// MSQeury object. +/// MSQuery object. /// Before a pull is allowed to run, one operation to send all pending requests on the /// specified table will be sent to the server. If a pending request for this table fails, /// the pull will be cancelled -(NSOperation *)pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(MSSyncBlock)completion; +/// Initiates a request to go to the server and get a set of records matching the specified +/// MSQuery object. +/// Before a pull is allowed to run, one operation to send all pending requests on the +/// specified table will be sent to the server. If a pending request for this table fails, +/// the pull will be cancelled +-(NSOperation *)pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId pullSettings:(MSPullSettings *)pullSettings completion:(MSSyncBlock)completion; + /// Removes all records in the local cache that match the results of the specified query. /// If query is nil, all records in the local table will be removed. /// Before local data is removed, a check will be made for pending operations on this table. If diff --git a/sdk/iOS/src/MSSyncTable.m b/sdk/iOS/src/MSSyncTable.m index 844fee49d..14167820d 100644 --- a/sdk/iOS/src/MSSyncTable.m +++ b/sdk/iOS/src/MSSyncTable.m @@ -61,7 +61,12 @@ -(void)delete:(NSDictionary *)item completion:(MSSyncBlock)completion -(NSOperation *)pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(MSSyncBlock)completion { - return [self.client.syncContext pullWithQuery:query queryId:queryId completion:completion]; + return [self pullWithQuery:query queryId:queryId pullSettings:nil completion:completion]; +} + +-(NSOperation *)pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId pullSettings:(id)pullSettings completion:(MSSyncBlock)completion +{ + return [self.client.syncContext pullWithQuery:query queryId:queryId pullSettings:pullSettings completion:completion]; } -(NSOperation *)purgeWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion diff --git a/sdk/iOS/test/MSSyncTableTests.m b/sdk/iOS/test/MSSyncTableTests.m index 533955225..94f41bb9c 100644 --- a/sdk/iOS/test/MSSyncTableTests.m +++ b/sdk/iOS/test/MSSyncTableTests.m @@ -1584,6 +1584,142 @@ -(void) testPullWithFetchLimitNotMultipleOfFifty @"Invalid URL: %@", thirdRequest.URL.absoluteString); } +-(void) testPullTable_UsePullSettings_NegativePageSize +{ + MSPullSettings *pullSettings = [MSPullSettings new]; + pullSettings.pageSize = -1; + + NSString *pullRequest = [self getPullTableRequestWithPullSettings:pullSettings usePullSettings:YES]; + + XCTAssertEqualObjects(pullRequest, @"https://someUrl/tables/TodoNoVersion?$top=50&__includeDeleted=true&$skip=0&__systemProperties=__deleted", "Incorrect pull request"); +} + +-(void) testPullTable_UsePullSettings_ZeroPageSize +{ + MSPullSettings *pullSettings = [MSPullSettings new]; + pullSettings.pageSize = 0; + + NSString *pullRequest = [self getPullTableRequestWithPullSettings:pullSettings usePullSettings:YES]; + + XCTAssertEqualObjects(pullRequest, @"https://someUrl/tables/TodoNoVersion?$top=50&__includeDeleted=true&$skip=0&__systemProperties=__deleted", "Incorrect pull request"); +} + +-(void) testPullTable_UsePullSettings_ValidPageSize +{ + MSPullSettings *pullSettings = [MSPullSettings new]; + pullSettings.pageSize = 1; + + NSString *pullRequest = [self getPullTableRequestWithPullSettings:pullSettings usePullSettings:YES]; + + XCTAssertEqualObjects(pullRequest, @"https://someUrl/tables/TodoNoVersion?$top=1&__includeDeleted=true&$skip=0&__systemProperties=__deleted", "Incorrect pull request"); +} + +-(void) testPullTable_PageOptionsNil +{ + NSString *pullRequest = [self getPullTableRequestWithPullSettings:nil usePullSettings:YES]; + + XCTAssertEqualObjects(pullRequest, @"https://someUrl/tables/TodoNoVersion?$top=50&__includeDeleted=true&$skip=0&__systemProperties=__deleted", "Incorrect pull request"); +} + +-(void) testPullTable_NoPageOptions +{ + NSString *pullRequest = [self getPullTableRequestWithPullSettings:nil usePullSettings:NO]; + + XCTAssertEqualObjects(pullRequest, @"https://someUrl/tables/TodoNoVersion?$top=50&__includeDeleted=true&$skip=0&__systemProperties=__deleted", "Incorrect pull request"); +} + +-(NSString *) getPullTableRequestWithPullSettings:(MSPullSettings *)pullSettings usePullSettings:(BOOL)shouldUsePullSettings +{ + MSTestFilter *testFilter = [MSTestFilter testFilterWithStatusCode:200 data:nil]; + __block NSString *pullRequest = nil; + + testFilter.onInspectRequest = ^(NSURLRequest *request) { + pullRequest = request.URL.absoluteString; + return request; + }; + + MSClient *filteredClient = [client clientWithFilter:testFilter]; + MSSyncTable *todoTable = [filteredClient syncTableWithName:TodoTableNoVersion]; + MSQuery *query = [[MSQuery alloc] initWithSyncTable:todoTable]; + + if (shouldUsePullSettings) { + [[todoTable pullWithQuery:query queryId:nil pullSettings:pullSettings completion:nil] waitUntilFinished]; + } + else { + [[todoTable pullWithQuery:query queryId:nil completion:nil] waitUntilFinished]; + } + + return pullRequest; +} + +-(void) testPullSyncContext_UsePullSettings_NegativePageSize +{ + MSPullSettings *pullSettings = [MSPullSettings new]; + pullSettings.pageSize = -1; + + NSString *pullRequest = [self getPullSyncContextRequestWithPullSettings:pullSettings usePullSettings:YES]; + + XCTAssertEqualObjects(pullRequest, @"https://someUrl/tables/TodoNoVersion?$top=50&__includeDeleted=true&$skip=0&__systemProperties=__deleted", "Incorrect pull request"); +} + +-(void) testPullSyncContext_UsePullSettings_ZeroPageSize +{ + MSPullSettings *pullSettings = [MSPullSettings new]; + pullSettings.pageSize = 0; + + NSString *pullRequest = [self getPullSyncContextRequestWithPullSettings:pullSettings usePullSettings:YES]; + + XCTAssertEqualObjects(pullRequest, @"https://someUrl/tables/TodoNoVersion?$top=50&__includeDeleted=true&$skip=0&__systemProperties=__deleted", "Incorrect pull request"); +} + +-(void) testPullSyncContext_UsePullSettings_ValidPageSize +{ + MSPullSettings *pullSettings = [MSPullSettings new]; + pullSettings.pageSize = 1; + + NSString *pullRequest = [self getPullSyncContextRequestWithPullSettings:pullSettings usePullSettings:YES]; + + XCTAssertEqualObjects(pullRequest, @"https://someUrl/tables/TodoNoVersion?$top=1&__includeDeleted=true&$skip=0&__systemProperties=__deleted", "Incorrect pull request"); +} + +-(void) testPullSyncContext_PageOptionsNil +{ + NSString *pullRequest = [self getPullSyncContextRequestWithPullSettings:nil usePullSettings:YES]; + + XCTAssertEqualObjects(pullRequest, @"https://someUrl/tables/TodoNoVersion?$top=50&__includeDeleted=true&$skip=0&__systemProperties=__deleted", "Incorrect pull request"); +} + +-(void) testPullSyncContext_NoPageOptions +{ + NSString *pullRequest = [self getPullSyncContextRequestWithPullSettings:nil usePullSettings:NO]; + + XCTAssertEqualObjects(pullRequest, @"https://someUrl/tables/TodoNoVersion?$top=50&__includeDeleted=true&$skip=0&__systemProperties=__deleted", "Incorrect pull request"); +} + +-(NSString *) getPullSyncContextRequestWithPullSettings:(MSPullSettings *)pullSettings usePullSettings:(BOOL)shouldUsePullSettings +{ + MSTestFilter *testFilter = [MSTestFilter testFilterWithStatusCode:200 data:nil]; + __block NSString *pullRequest = nil; + + testFilter.onInspectRequest = ^(NSURLRequest *request) { + pullRequest = request.URL.absoluteString; + return request; + }; + + MSClient *filteredClient = [client clientWithFilter:testFilter]; + MSSyncTable *todoTable = [filteredClient syncTableWithName:TodoTableNoVersion]; + MSQuery *query = [[MSQuery alloc] initWithSyncTable:todoTable]; + + if (shouldUsePullSettings) { + [[filteredClient.syncContext pullWithQuery:query queryId:nil pullSettings:pullSettings completion:nil] waitUntilFinished]; + } + else { + [[filteredClient.syncContext pullWithQuery:query queryId:nil completion:nil] waitUntilFinished]; + } + + return pullRequest; +} + -(void) testPullWithFetchOffset { // Pull should start with a $skip and the skip should be incremented by the number of records pulled