Skip to content

Commit f9b5839

Browse files
Attempt at fixing issue with getting next and previous push id (#8817)
1 parent 61766c2 commit f9b5839

File tree

4 files changed

+288
-40
lines changed

4 files changed

+288
-40
lines changed

FirebaseDatabase/Sources/Api/FIRDatabaseQuery.m

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ - (FIRDatabaseQuery *)queryStartingAfterValue:(id)startAfterValue {
207207

208208
- (FIRDatabaseQuery *)queryStartingAfterValue:(id)startAfterValue
209209
childKey:(NSString *)childKey {
210+
NSString *methodName = @"queryStartingAfterValue:childKey:";
210211
if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
211212
if (childKey != nil) {
212213
@throw [[NSException alloc]
@@ -218,16 +219,16 @@ - (FIRDatabaseQuery *)queryStartingAfterValue:(id)startAfterValue
218219
userInfo:nil];
219220
}
220221
if ([startAfterValue isKindOfClass:[NSString class]]) {
221-
startAfterValue = [FNextPushId successor:startAfterValue];
222+
startAfterValue = [FNextPushId from:methodName
223+
successor:startAfterValue];
222224
}
223225
} else {
224226
if (childKey == nil) {
225227
childKey = [FUtilities maxName];
226228
} else {
227-
childKey = [FNextPushId successor:childKey];
229+
childKey = [FNextPushId from:methodName successor:childKey];
228230
}
229231
}
230-
NSString *methodName = @"queryStartingAfterValue:childKey:";
231232
if (childKey != nil && ![childKey isEqual:[FUtilities maxName]]) {
232233
[FValidation validateFrom:methodName validKey:childKey];
233234
}
@@ -294,6 +295,7 @@ - (FIRDatabaseQuery *)queryEndingBeforeValue:(id)endValue {
294295

295296
- (FIRDatabaseQuery *)queryEndingBeforeValue:(id)endValue
296297
childKey:(NSString *)childKey {
298+
NSString *methodName = @"queryEndingBeforeValue:childKey:";
297299
if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
298300
if (childKey != nil) {
299301
@throw [[NSException alloc]
@@ -304,16 +306,15 @@ - (FIRDatabaseQuery *)queryEndingBeforeValue:(id)endValue
304306
userInfo:nil];
305307
}
306308
if ([endValue isKindOfClass:[NSString class]]) {
307-
endValue = [FNextPushId predecessor:endValue];
309+
endValue = [FNextPushId from:methodName predecessor:endValue];
308310
}
309311
} else {
310312
if (childKey == nil) {
311313
childKey = [FUtilities minName];
312314
} else {
313-
childKey = [FNextPushId predecessor:childKey];
315+
childKey = [FNextPushId from:methodName predecessor:childKey];
314316
}
315317
}
316-
NSString *methodName = @"queryEndingBeforeValue:childKey:";
317318
if (childKey != nil && ![childKey isEqual:[FUtilities minName]]) {
318319
[FValidation validateFrom:methodName validKey:childKey];
319320
}

FirebaseDatabase/Sources/Utilities/FNextPushId.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020

2121
+ (NSString *)get:(NSTimeInterval)now;
2222

23-
+ (NSString *)successor:(NSString *)key;
23+
+ (NSString *)from:(NSString *)fn successor:(NSString *)key;
2424

25-
+ (NSString *)predecessor:(NSString *)key;
25+
+ (NSString *)from:(NSString *)fn predecessor:(NSString *)key;
2626

2727
@end

FirebaseDatabase/Sources/Utilities/FNextPushId.m

Lines changed: 176 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,22 @@
1616

1717
#import "FirebaseDatabase/Sources/Utilities/FNextPushId.h"
1818
#import "FirebaseDatabase/Sources/Utilities/FUtilities.h"
19+
#import "FirebaseDatabase/Sources/Utilities/FValidation.h"
1920

2021
static NSString *const PUSH_CHARS =
2122
@"-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
2223

23-
static NSString *const MIN_PUSH_CHAR = @"-";
24+
static NSString *const MIN_PUSH_CHAR = @" ";
2425

25-
static NSString *const MAX_PUSH_CHAR = @"z";
26+
static NSString *const MAX_PUSH_CHAR = @"\uFFFF";
2627

2728
static NSInteger const MAX_KEY_LEN = 786;
2829

30+
static unichar const LOW_SURROGATE_PAIR_START = 0xDC00;
31+
static unichar const LOW_SURROGATE_PAIR_END = 0xDFFF;
32+
static unichar const HIGH_SURROGATE_PAIR_START = 0xD800;
33+
static unichar const HIGH_SURROGATE_PAIR_END = 0xDBFF;
34+
2935
@implementation FNextPushId
3036

3137
+ (NSString *)get:(NSTimeInterval)currentTime {
@@ -65,7 +71,8 @@ + (NSString *)get:(NSTimeInterval)currentTime {
6571
return [NSString stringWithString:id];
6672
}
6773

68-
+ (NSString *)successor:(NSString *_Nonnull)key {
74+
+ (NSString *)from:(NSString *)fn successor:(NSString *_Nonnull)key {
75+
[FValidation validateFrom:fn validKey:key];
6976
NSInteger keyAsInt;
7077
if ([FUtilities tryParseString:key asInt:&keyAsInt]) {
7178
if (keyAsInt == [FUtilities int32max]) {
@@ -93,19 +100,91 @@ + (NSString *)successor:(NSString *_Nonnull)key {
93100
return [FUtilities maxName];
94101
}
95102

96-
NSString *source =
97-
[NSString stringWithFormat:@"%C", [next characterAtIndex:i]];
98-
NSInteger sourceIndex = [PUSH_CHARS rangeOfString:source].location;
99-
NSString *sourcePlusOne = [NSString
100-
stringWithFormat:@"%C", [PUSH_CHARS characterAtIndex:sourceIndex + 1]];
103+
unichar character = [next characterAtIndex:i];
104+
unichar plusOne = character + 1;
105+
BOOL removePreviousCharacter = NO;
106+
BOOL replaceWithLowestSurrogatePair = NO;
107+
switch (plusOne) {
108+
case 0x23: // '#'
109+
case 0x24: // '$'
110+
plusOne = 0x25;
111+
break;
112+
113+
case 0x2E: // '.'
114+
case 0x2F: // '/'
115+
plusOne = 0x30;
116+
break;
117+
118+
case 0x5B: // '['
119+
plusOne = 0x5C;
120+
break;
121+
122+
case 0x5D: // ']'
123+
plusOne = 0x5E;
124+
break;
101125

102-
[next replaceCharactersInRange:NSMakeRange(i, i + 1)
126+
case 0x7F: // control character: del
127+
plusOne = 0x80;
128+
break;
129+
130+
case HIGH_SURROGATE_PAIR_START: // 0xD800
131+
// We added one to 0xD7FF and entered surrogate pair zone
132+
// Replace with the lowest surrogate pair here
133+
replaceWithLowestSurrogatePair = YES;
134+
135+
case LOW_SURROGATE_PAIR_END + 1: // 0xE000
136+
// If the previous character is the highest surrogate value
137+
// then we increment to the value 0xE000 by replacing the surrogate
138+
// pair by the single value 0xE000 (the value of plusOne)
139+
// Otherwise we increment the high surrogate value and set the low
140+
// surrogate value to the lowest.
141+
if (i == 0) {
142+
// Error, encountered low surrogate without matching high surrogate
143+
} else {
144+
unichar high = [next characterAtIndex:i - 1];
145+
if (high == HIGH_SURROGATE_PAIR_END) { /* highest value for the high
146+
part of the pair */
147+
// Replace pair with 0xE000 (the value of plusOne)
148+
removePreviousCharacter = YES;
149+
} else {
150+
high += 1;
151+
NSString *highStr = [NSString stringWithFormat:@"%C", high];
152+
153+
[next replaceCharactersInRange:NSMakeRange(i - 1, i)
154+
withString:highStr];
155+
plusOne = LOW_SURROGATE_PAIR_START; /* lowest value for the low
156+
part of the pair */
157+
}
158+
}
159+
break;
160+
}
161+
162+
NSString *sourcePlusOne =
163+
replaceWithLowestSurrogatePair
164+
? [NSString stringWithFormat:@"%C%C", HIGH_SURROGATE_PAIR_START,
165+
LOW_SURROGATE_PAIR_START]
166+
: [NSString stringWithFormat:@"%C", plusOne];
167+
168+
NSInteger replaceLocation = i;
169+
NSInteger replaceLength = 1;
170+
if (removePreviousCharacter) {
171+
--replaceLocation;
172+
++replaceLength;
173+
}
174+
175+
[next replaceCharactersInRange:NSMakeRange(replaceLocation, replaceLength)
103176
withString:sourcePlusOne];
104-
return [next substringWithRange:NSMakeRange(0, i + 1)];
177+
NSInteger length = i + 1;
178+
if (removePreviousCharacter) {
179+
--length;
180+
} else if (replaceWithLowestSurrogatePair) {
181+
++length;
182+
}
183+
return [next substringWithRange:NSMakeRange(0, length)];
105184
}
106185

107-
// `key` is assumed to be non-empty.
108-
+ (NSString *)predecessor:(NSString *_Nonnull)key {
186+
+ (NSString *)from:(NSString *)fn predecessor:(NSString *_Nonnull)key {
187+
[FValidation validateFrom:fn validKey:key];
109188
NSInteger keyAsInt;
110189
if ([FUtilities tryParseString:key asInt:&keyAsInt]) {
111190
if (keyAsInt == [FUtilities int32min]) {
@@ -129,13 +208,91 @@ + (NSString *)predecessor:(NSString *_Nonnull)key {
129208
// Replace the last character with its immedate predecessor, and fill the
130209
// suffix of the key with MAX_PUSH_CHAR. This is the lexicographically
131210
// largest possible key smaller than `key`.
132-
unichar curr = [next characterAtIndex:next.length - 1];
133-
NSRange dstRange = NSMakeRange([next length] - 1, 1);
134-
NSRange srcRange =
135-
[PUSH_CHARS rangeOfString:[NSString stringWithFormat:@"%C", curr]];
136-
srcRange.location -= 1;
137-
[next replaceCharactersInRange:dstRange
138-
withString:[PUSH_CHARS substringWithRange:srcRange]];
211+
NSUInteger i = next.length - 1;
212+
unichar character = [next characterAtIndex:i];
213+
unichar minusOne = character - 1;
214+
BOOL removePreviousCharacter = NO;
215+
BOOL replaceWithHighestSurrogatePair = NO;
216+
switch (minusOne) {
217+
// NOTE: We already handled the case of min char (0x20)
218+
case 0x23: // '#'
219+
case 0x24: // '$'
220+
minusOne = 0x22;
221+
break;
222+
223+
case 0x2E: // '.'
224+
case 0x2F: // '/'
225+
minusOne = 0x2D;
226+
break;
227+
228+
case 0x5B: // '['
229+
minusOne = 0x5A;
230+
break;
231+
232+
case 0x5D: // ']'
233+
minusOne = 0x5C;
234+
break;
235+
236+
case 0x7F: // control character: del
237+
minusOne = 0x7E;
238+
break;
239+
240+
case LOW_SURROGATE_PAIR_END: // 0xDFFF
241+
// Previously we had 0xE000 which is a single utf16 character,
242+
// this needs to be replaced with the highest surrogate pair:
243+
replaceWithHighestSurrogatePair = YES;
244+
break;
245+
246+
case HIGH_SURROGATE_PAIR_END: // 0xDBFF
247+
// If the previous character is the lowest high surrogate value
248+
// then we decrement to the non-surrogate value 0xD7FF by replacing the
249+
// surrogate pair by the single value 0xD7FF (HIGH_SURROGATE_PAIR_START
250+
// - 1) Otherwise we decrement the high surrogate value and set the low
251+
// surrogate value to the highest.
252+
if (i == 0) {
253+
// Error, found low surrogate without matching high surrogate
254+
} else {
255+
unichar high = [next characterAtIndex:i - 1];
256+
if (high == HIGH_SURROGATE_PAIR_START) { /* lowest value for the
257+
high part of the pair */
258+
// Replace pair with single 0xD7FF value
259+
removePreviousCharacter = YES;
260+
minusOne = HIGH_SURROGATE_PAIR_START - 1;
261+
} else {
262+
high -= 1;
263+
NSString *highStr = [NSString stringWithFormat:@"%C", high];
264+
265+
[next replaceCharactersInRange:NSMakeRange(i - 1, i)
266+
withString:highStr];
267+
minusOne = LOW_SURROGATE_PAIR_END; /* highest value for the low
268+
part of the pair */
269+
}
270+
}
271+
break;
272+
}
273+
274+
NSString *sourceMinusOne =
275+
replaceWithHighestSurrogatePair
276+
? [NSString stringWithFormat:@"%C%C", HIGH_SURROGATE_PAIR_END,
277+
LOW_SURROGATE_PAIR_END]
278+
: [NSString stringWithFormat:@"%C", minusOne];
279+
280+
NSInteger replaceLocation = i;
281+
NSInteger replaceLength = 1;
282+
if (removePreviousCharacter) {
283+
--replaceLocation;
284+
++replaceLength;
285+
}
286+
287+
[next replaceCharactersInRange:NSMakeRange(replaceLocation, replaceLength)
288+
withString:sourceMinusOne];
289+
290+
NSInteger length = i + 1;
291+
if (removePreviousCharacter) {
292+
--length;
293+
} else if (replaceWithHighestSurrogatePair) {
294+
++length;
295+
}
139296
return [next stringByPaddingToLength:MAX_KEY_LEN
140297
withString:MAX_PUSH_CHAR
141298
startingAtIndex:0];

0 commit comments

Comments
 (0)