/* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "FQueryParams.h" #import "FValidation.h" #import "FConstants.h" #import "FIndex.h" #import "FPriorityIndex.h" #import "FUtilities.h" #import "FNodeFilter.h" #import "FIndexedFilter.h" #import "FLimitedFilter.h" #import "FRangedFilter.h" #import "FNode.h" #import "FSnapshotUtilities.h" @interface FQueryParams () @property (nonatomic, readwrite) BOOL limitSet; @property (nonatomic, readwrite) NSInteger limit; @property (nonatomic, strong, readwrite) NSString *viewFrom; /** * indexStartValue is anything you can store as a priority / value. */ @property (nonatomic, strong, readwrite) id indexStartValue; @property (nonatomic, strong, readwrite) NSString *indexStartKey; /** * indexStartValue is anything you can store as a priority / value. */ @property (nonatomic, strong, readwrite) id indexEndValue; @property (nonatomic, strong, readwrite) NSString *indexEndKey; @property (nonatomic, strong, readwrite) id index; @end @implementation FQueryParams + (FQueryParams *) defaultInstance { static FQueryParams *defaultParams = nil; static dispatch_once_t defaultParamsToken; dispatch_once(&defaultParamsToken, ^{ defaultParams = [[FQueryParams alloc] init]; }); return defaultParams; } - (id)init { self = [super init]; if (self) { self->_limitSet = NO; self->_limit = 0; self->_viewFrom = nil; self->_indexStartValue = nil; self->_indexStartKey = nil; self->_indexEndValue = nil; self->_indexEndKey = nil; self->_index = [FPriorityIndex priorityIndex]; } return self; } /** * Only valid if hasStart is true */ - (id) indexStartValue { NSAssert([self hasStart], @"Only valid if start has been set"); return _indexStartValue; } /** * Only valid if hasStart is true. * @return The starting key name for the range defined by these query parameters */ - (NSString *) indexStartKey { NSAssert([self hasStart], @"Only valid if start has been set"); if (_indexStartKey == nil) { return [FUtilities minName]; } else { return _indexStartKey; } } /** * Only valid if hasEnd is true. */ - (id) indexEndValue { NSAssert([self hasEnd], @"Only valid if end has been set"); return _indexEndValue; } /** * Only valid if hasEnd is true. * @return The end key name for the range defined by these query parameters */ - (NSString *) indexEndKey { NSAssert([self hasEnd], @"Only valid if end has been set"); if (_indexEndKey == nil) { return [FUtilities maxName]; } else { return _indexEndKey; } } /** * @return true if a limit has been set and has been explicitly anchored */ - (BOOL) hasAnchoredLimit { return self.limitSet && self.viewFrom != nil; } /** * Only valid to call if limitSet returns true */ - (NSInteger) limit { NSAssert(self.limitSet, @"Only valid if limit has been set"); return _limit; } - (BOOL)hasStart { return self->_indexStartValue != nil; } - (BOOL)hasEnd { return self->_indexEndValue != nil; } - (id) copyWithZone:(NSZone *)zone { // Immutable return self; } - (id) mutableCopy { FQueryParams* other = [[[self class] alloc] init]; // Maybe need to do extra copying here other->_limitSet = _limitSet; other->_limit = _limit; other->_indexStartValue = _indexStartValue; other->_indexStartKey = _indexStartKey; other->_indexEndValue = _indexEndValue; other->_indexEndKey = _indexEndKey; other->_viewFrom = _viewFrom; other->_index = _index; return other; } - (FQueryParams *) limitTo:(NSInteger)newLimit { FQueryParams *newParams = [self mutableCopy]; newParams->_limitSet = YES; newParams->_limit = newLimit; newParams->_viewFrom = nil; return newParams; } - (FQueryParams *) limitToFirst:(NSInteger)newLimit { FQueryParams *newParams = [self mutableCopy]; newParams->_limitSet = YES; newParams->_limit = newLimit; newParams->_viewFrom = kFQPViewFromLeft; return newParams; } - (FQueryParams *) limitToLast:(NSInteger)newLimit { FQueryParams *newParams = [self mutableCopy]; newParams->_limitSet = YES; newParams->_limit = newLimit; newParams->_viewFrom = kFQPViewFromRight; return newParams; } - (FQueryParams *) startAt:(id)indexValue childKey:(NSString *)key { NSAssert([indexValue isLeafNode] || [indexValue isEmpty], nil); FQueryParams *newParams = [self mutableCopy]; newParams->_indexStartValue = indexValue; newParams->_indexStartKey = key; return newParams; } - (FQueryParams *) startAt:(id)indexValue { return [self startAt:indexValue childKey:nil]; } - (FQueryParams *) endAt:(id)indexValue childKey:(NSString *)key { NSAssert([indexValue isLeafNode] || [indexValue isEmpty], nil); FQueryParams *newParams = [self mutableCopy]; newParams->_indexEndValue = indexValue; newParams->_indexEndKey = key; return newParams; } - (FQueryParams *) endAt:(id)indexValue { return [self endAt:indexValue childKey:nil]; } - (FQueryParams *) orderBy:(id)newIndex { FQueryParams *newParams = [self mutableCopy]; newParams->_index = newIndex; return newParams; } - (NSDictionary *) wireProtocolParams { NSMutableDictionary* dict = [[NSMutableDictionary alloc] init]; if ([self hasStart]) { [dict setObject:[self.indexStartValue valForExport:YES] forKey:kFQPIndexStartValue]; // Don't use property as it will be [MIN-NAME] if (self->_indexStartKey != nil) { [dict setObject:self->_indexStartKey forKey:kFQPIndexStartName]; } } if ([self hasEnd]) { [dict setObject:[self.indexEndValue valForExport:YES] forKey:kFQPIndexEndValue]; // Don't use property as it will be [MAX-NAME] if (self->_indexEndKey != nil) { [dict setObject:self->_indexEndKey forKey:kFQPIndexEndName]; } } if (self.limitSet) { [dict setObject:[NSNumber numberWithInteger:self.limit] forKey:kFQPLimit]; NSString *vf = self.viewFrom; if (vf == nil) { // limit() rather than limitToFirst or limitToLast was called. // This means that only one of startSet or endSet is true. Use them // to calculate which side of the view to anchor to. If neither is set, // Anchor to end if ([self hasStart]) { vf = kFQPViewFromLeft; } else { vf = kFQPViewFromRight; } } [dict setObject:vf forKey:kFQPViewFrom]; } // For now, priority index is the default, so we only specify if it's some other index. if (![self.index isEqual:[FPriorityIndex priorityIndex]]) { [dict setObject:[self.index queryDefinition] forKey:kFQPIndex]; } return dict; } + (FQueryParams *)fromQueryObject:(NSDictionary *)dict { if (dict.count == 0) { return [FQueryParams defaultInstance]; } FQueryParams *params = [[FQueryParams alloc] init]; if (dict[kFQPLimit] != nil) { params->_limitSet = YES; params->_limit = [dict[kFQPLimit] integerValue]; } if (dict[kFQPIndexStartValue] != nil) { params->_indexStartValue = [FSnapshotUtilities nodeFrom:dict[kFQPIndexStartValue]]; if (dict[kFQPIndexStartName] != nil) { params->_indexStartKey = dict[kFQPIndexStartName]; } } if (dict[kFQPIndexEndValue] != nil) { params->_indexEndValue = [FSnapshotUtilities nodeFrom:dict[kFQPIndexEndValue]]; if (dict[kFQPIndexEndName] != nil) { params->_indexEndKey = dict[kFQPIndexEndName]; } } if (dict[kFQPViewFrom] != nil) { NSString *viewFrom = dict[kFQPViewFrom]; if (![viewFrom isEqualToString:kFQPViewFromLeft] && ![viewFrom isEqualToString:kFQPViewFromRight]) { [NSException raise:NSInvalidArgumentException format:@"Unknown view from paramter: %@", viewFrom]; } params->_viewFrom = viewFrom; } NSString *index = dict[kFQPIndex]; if (index != nil) { params->_index = [FIndex indexFromQueryDefinition:index]; } return params; } - (BOOL) isViewFromLeft { if (self.viewFrom != nil) { // Not null, we can just check return [self.viewFrom isEqualToString:kFQPViewFromLeft]; } else { // If start is set, it's view from left. Otherwise not. return self.hasStart; } } - (id) nodeFilter { if (self.loadsAllData) { return [[FIndexedFilter alloc] initWithIndex:self.index]; } else if (self.limitSet) { return [[FLimitedFilter alloc] initWithQueryParams:self]; } else { return [[FRangedFilter alloc] initWithQueryParams:self]; } } - (BOOL) isValid { return !(self.hasStart && self.hasEnd && self.limitSet && !self.hasAnchoredLimit); } - (BOOL) loadsAllData { return !(self.hasStart || self.hasEnd || self.limitSet); } - (BOOL) isDefault { return [self loadsAllData] && [self.index isEqual:[FPriorityIndex priorityIndex]]; } - (NSString *) description { return [[self wireProtocolParams] description]; } - (BOOL) isEqual:(id)obj { if (self == obj) { return YES; } if (![obj isKindOfClass:[self class]]) { return NO; } FQueryParams *other = (FQueryParams *)obj; if (self->_limitSet != other->_limitSet) return NO; if (self->_limit != other->_limit) return NO; if ((self->_index != other->_index) && ![self->_index isEqual:other->_index]) return NO; if ((self->_indexStartKey != other->_indexStartKey) && ![self->_indexStartKey isEqualToString:other->_indexStartKey]) return NO; if ((self->_indexStartValue != other->_indexStartValue) && ![self->_indexStartValue isEqual:other->_indexStartValue]) return NO; if ((self->_indexEndKey != other->_indexEndKey) && ![self->_indexEndKey isEqualToString:other->_indexEndKey]) return NO; if ((self->_indexEndValue != other->_indexEndValue) && ![self->_indexEndValue isEqual:other->_indexEndValue]) return NO; if ([self isViewFromLeft] != [other isViewFromLeft]) return NO; return YES; } - (NSUInteger) hash { NSUInteger result = _limitSet ? _limit : 0; result = 31 * result + ([self isViewFromLeft] ? 1231 : 1237); result = 31 * result + [_indexStartKey hash]; result = 31 * result + [_indexStartValue hash]; result = 31 * result + [_indexEndKey hash]; result = 31 * result + [_indexEndValue hash]; result = 31 * result + [_index hash]; return result; } @end