Skip to content

Commit dd8b061

Browse files
committed
Merge branch 'master' into mila/BloomFilter
2 parents 0ccea52 + 37dd6f6 commit dd8b061

File tree

9 files changed

+422
-118
lines changed

9 files changed

+422
-118
lines changed

.changeset/empty-doors-brush.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@firebase/database': patch
3+
'@firebase/database-compat': patch
4+
---
5+
6+
Use new wire protocol parameters for startAfter, endBefore.

.changeset/rotten-peaches-poke.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

packages/database-compat/test/query.test.ts

+52-10
Original file line numberDiff line numberDiff line change
@@ -375,34 +375,76 @@ describe('Query Tests', () => {
375375
expect(queryId(path)).to.equal('default');
376376

377377
expect(queryId(path.startAt('pri', 'name'))).to.equal(
378-
'{"sn":"name","sp":"pri"}'
378+
'{"sin":true,"sn":"name","sp":"pri"}'
379379
);
380380
expect(queryId(path.startAfter('pri', 'name'))).to.equal(
381-
'{"sn":"name-","sp":"pri"}'
381+
'{"sin":false,"sn":"name","sp":"pri"}'
382382
);
383+
expect(queryId(path.endAt('pri', 'name'))).to.equal(
384+
'{"ein":true,"en":"name","ep":"pri"}'
385+
);
386+
expect(queryId(path.endBefore('pri', 'name'))).to.equal(
387+
'{"ein":false,"en":"name","ep":"pri"}'
388+
);
389+
383390
expect(queryId(path.startAt('spri').endAt('epri'))).to.equal(
384-
'{"ep":"epri","sp":"spri"}'
391+
'{"ein":true,"ep":"epri","sin":true,"sp":"spri"}'
392+
);
393+
expect(queryId(path.startAt('spri').endBefore('epri'))).to.equal(
394+
'{"ein":false,"en":"[MIN_NAME]","ep":"epri","sin":true,"sp":"spri"}'
385395
);
386396
expect(queryId(path.startAfter('spri').endAt('epri'))).to.equal(
387-
'{"ep":"epri","sn":"[MAX_NAME]","sp":"spri"}'
397+
'{"ein":true,"ep":"epri","sin":false,"sn":"[MAX_NAME]","sp":"spri"}'
398+
);
399+
expect(queryId(path.startAfter('spri').endBefore('epri'))).to.equal(
400+
'{"ein":false,"en":"[MIN_NAME]","ep":"epri","sin":false,"sn":"[MAX_NAME]","sp":"spri"}'
388401
);
402+
389403
expect(
390404
queryId(path.startAt('spri', 'sname').endAt('epri', 'ename'))
391-
).to.equal('{"en":"ename","ep":"epri","sn":"sname","sp":"spri"}');
405+
).to.equal(
406+
'{"ein":true,"en":"ename","ep":"epri","sin":true,"sn":"sname","sp":"spri"}'
407+
);
408+
expect(
409+
queryId(path.startAt('spri', 'sname').endBefore('epri', 'ename'))
410+
).to.equal(
411+
'{"ein":false,"en":"ename","ep":"epri","sin":true,"sn":"sname","sp":"spri"}'
412+
);
392413
expect(
393414
queryId(path.startAfter('spri', 'sname').endAt('epri', 'ename'))
394-
).to.equal('{"en":"ename","ep":"epri","sn":"sname-","sp":"spri"}');
415+
).to.equal(
416+
'{"ein":true,"en":"ename","ep":"epri","sin":false,"sn":"sname","sp":"spri"}'
417+
);
418+
expect(
419+
queryId(path.startAfter('spri', 'sname').endBefore('epri', 'ename'))
420+
).to.equal(
421+
'{"ein":false,"en":"ename","ep":"epri","sin":false,"sn":"sname","sp":"spri"}'
422+
);
423+
395424
expect(queryId(path.startAt('pri').limitToFirst(100))).to.equal(
396-
'{"l":100,"sp":"pri","vf":"l"}'
425+
'{"l":100,"sin":true,"sp":"pri","vf":"l"}'
397426
);
398427
expect(queryId(path.startAfter('pri').limitToFirst(100))).to.equal(
399-
'{"l":100,"sn":"[MAX_NAME]","sp":"pri","vf":"l"}'
428+
'{"l":100,"sin":false,"sn":"[MAX_NAME]","sp":"pri","vf":"l"}'
429+
);
430+
expect(queryId(path.endAt('pri').limitToLast(100))).to.equal(
431+
'{"ein":true,"ep":"pri","l":100,"vf":"r"}'
400432
);
433+
expect(queryId(path.endBefore('pri').limitToLast(100))).to.equal(
434+
'{"ein":false,"en":"[MIN_NAME]","ep":"pri","l":100,"vf":"r"}'
435+
);
436+
401437
expect(queryId(path.startAt('bar').orderByChild('foo'))).to.equal(
402-
'{"i":"foo","sp":"bar"}'
438+
'{"i":"foo","sin":true,"sp":"bar"}'
403439
);
404440
expect(queryId(path.startAfter('bar').orderByChild('foo'))).to.equal(
405-
'{"i":"foo","sn":"[MAX_NAME]","sp":"bar"}'
441+
'{"i":"foo","sin":false,"sn":"[MAX_NAME]","sp":"bar"}'
442+
);
443+
expect(queryId(path.endAt('bar').orderByChild('foo'))).to.equal(
444+
'{"ein":true,"ep":"bar","i":"foo"}'
445+
);
446+
expect(queryId(path.endBefore('bar').orderByChild('foo'))).to.equal(
447+
'{"ein":false,"en":"[MIN_NAME]","ep":"bar","i":"foo"}'
406448
);
407449
});
408450

packages/database/src/core/view/QueryParams.ts

+26-39
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import { KEY_INDEX } from '../snap/indexes/KeyIndex';
2222
import { PathIndex } from '../snap/indexes/PathIndex';
2323
import { PRIORITY_INDEX, PriorityIndex } from '../snap/indexes/PriorityIndex';
2424
import { VALUE_INDEX } from '../snap/indexes/ValueIndex';
25-
import { predecessor, successor } from '../util/NextPushId';
2625
import { MAX_NAME, MIN_NAME } from '../util/util';
2726

2827
import { IndexedFilter } from './filter/IndexedFilter';
@@ -36,8 +35,10 @@ import { RangedFilter } from './filter/RangedFilter';
3635
const enum WIRE_PROTOCOL_CONSTANTS {
3736
INDEX_START_VALUE = 'sp',
3837
INDEX_START_NAME = 'sn',
38+
INDEX_START_IS_INCLUSIVE = 'sin',
3939
INDEX_END_VALUE = 'ep',
4040
INDEX_END_NAME = 'en',
41+
INDEX_END_IS_INCLUSIVE = 'ein',
4142
LIMIT = 'l',
4243
VIEW_FROM = 'vf',
4344
VIEW_FROM_LEFT = 'l',
@@ -53,8 +54,10 @@ const enum REST_QUERY_CONSTANTS {
5354
PRIORITY_INDEX = '$priority',
5455
VALUE_INDEX = '$value',
5556
KEY_INDEX = '$key',
57+
START_AFTER = 'startAfter',
5658
START_AT = 'startAt',
5759
END_AT = 'endAt',
60+
END_BEFORE = 'endBefore',
5861
LIMIT_TO_FIRST = 'limitToFirst',
5962
LIMIT_TO_LAST = 'limitToLast'
6063
}
@@ -70,10 +73,10 @@ export class QueryParams {
7073
limitSet_ = false;
7174
startSet_ = false;
7275
startNameSet_ = false;
73-
startAfterSet_ = false;
76+
startAfterSet_ = false; // can only be true if startSet_ is true
7477
endSet_ = false;
7578
endNameSet_ = false;
76-
endBeforeSet_ = false;
79+
endBeforeSet_ = false; // can only be true if endSet_ is true
7780
limit_ = 0;
7881
viewFrom_ = '';
7982
indexStartValue_: unknown | null = null;
@@ -86,14 +89,6 @@ export class QueryParams {
8689
return this.startSet_;
8790
}
8891

89-
hasStartAfter(): boolean {
90-
return this.startAfterSet_;
91-
}
92-
93-
hasEndBefore(): boolean {
94-
return this.endBeforeSet_;
95-
}
96-
9792
/**
9893
* @returns True if it would return from left.
9994
*/
@@ -191,10 +186,12 @@ export class QueryParams {
191186
copy.limitSet_ = this.limitSet_;
192187
copy.limit_ = this.limit_;
193188
copy.startSet_ = this.startSet_;
189+
copy.startAfterSet_ = this.startAfterSet_;
194190
copy.indexStartValue_ = this.indexStartValue_;
195191
copy.startNameSet_ = this.startNameSet_;
196192
copy.indexStartName_ = this.indexStartName_;
197193
copy.endSet_ = this.endSet_;
194+
copy.endBeforeSet_ = this.endBeforeSet_;
198195
copy.indexEndValue_ = this.indexEndValue_;
199196
copy.endNameSet_ = this.endNameSet_;
200197
copy.indexEndName_ = this.indexEndName_;
@@ -274,19 +271,10 @@ export function queryParamsStartAfter(
274271
key?: string | null
275272
): QueryParams {
276273
let params: QueryParams;
277-
if (queryParams.index_ === KEY_INDEX) {
278-
if (typeof indexValue === 'string') {
279-
indexValue = successor(indexValue as string);
280-
}
274+
if (queryParams.index_ === KEY_INDEX || !!key) {
281275
params = queryParamsStartAt(queryParams, indexValue, key);
282276
} else {
283-
let childKey: string;
284-
if (key == null) {
285-
childKey = MAX_NAME;
286-
} else {
287-
childKey = successor(key);
288-
}
289-
params = queryParamsStartAt(queryParams, indexValue, childKey);
277+
params = queryParamsStartAt(queryParams, indexValue, MAX_NAME);
290278
}
291279
params.startAfterSet_ = true;
292280
return params;
@@ -318,20 +306,11 @@ export function queryParamsEndBefore(
318306
indexValue: unknown,
319307
key?: string | null
320308
): QueryParams {
321-
let childKey: string;
322309
let params: QueryParams;
323-
if (queryParams.index_ === KEY_INDEX) {
324-
if (typeof indexValue === 'string') {
325-
indexValue = predecessor(indexValue as string);
326-
}
310+
if (queryParams.index_ === KEY_INDEX || !!key) {
327311
params = queryParamsEndAt(queryParams, indexValue, key);
328312
} else {
329-
if (key == null) {
330-
childKey = MIN_NAME;
331-
} else {
332-
childKey = predecessor(key);
333-
}
334-
params = queryParamsEndAt(queryParams, indexValue, childKey);
313+
params = queryParamsEndAt(queryParams, indexValue, MIN_NAME);
335314
}
336315
params.endBeforeSet_ = true;
337316
return params;
@@ -374,18 +353,22 @@ export function queryParamsToRestQueryStringParameters(
374353
qs[REST_QUERY_CONSTANTS.ORDER_BY] = stringify(orderBy);
375354

376355
if (queryParams.startSet_) {
377-
qs[REST_QUERY_CONSTANTS.START_AT] = stringify(queryParams.indexStartValue_);
356+
const startParam = queryParams.startAfterSet_
357+
? REST_QUERY_CONSTANTS.START_AFTER
358+
: REST_QUERY_CONSTANTS.START_AT;
359+
qs[startParam] = stringify(queryParams.indexStartValue_);
378360
if (queryParams.startNameSet_) {
379-
qs[REST_QUERY_CONSTANTS.START_AT] +=
380-
',' + stringify(queryParams.indexStartName_);
361+
qs[startParam] += ',' + stringify(queryParams.indexStartName_);
381362
}
382363
}
383364

384365
if (queryParams.endSet_) {
385-
qs[REST_QUERY_CONSTANTS.END_AT] = stringify(queryParams.indexEndValue_);
366+
const endParam = queryParams.endBeforeSet_
367+
? REST_QUERY_CONSTANTS.END_BEFORE
368+
: REST_QUERY_CONSTANTS.END_AT;
369+
qs[endParam] = stringify(queryParams.indexEndValue_);
386370
if (queryParams.endNameSet_) {
387-
qs[REST_QUERY_CONSTANTS.END_AT] +=
388-
',' + stringify(queryParams.indexEndName_);
371+
qs[endParam] += ',' + stringify(queryParams.indexEndName_);
389372
}
390373
}
391374

@@ -411,12 +394,16 @@ export function queryParamsGetQueryObject(
411394
obj[WIRE_PROTOCOL_CONSTANTS.INDEX_START_NAME] =
412395
queryParams.indexStartName_;
413396
}
397+
obj[WIRE_PROTOCOL_CONSTANTS.INDEX_START_IS_INCLUSIVE] =
398+
!queryParams.startAfterSet_;
414399
}
415400
if (queryParams.endSet_) {
416401
obj[WIRE_PROTOCOL_CONSTANTS.INDEX_END_VALUE] = queryParams.indexEndValue_;
417402
if (queryParams.endNameSet_) {
418403
obj[WIRE_PROTOCOL_CONSTANTS.INDEX_END_NAME] = queryParams.indexEndName_;
419404
}
405+
obj[WIRE_PROTOCOL_CONSTANTS.INDEX_END_IS_INCLUSIVE] =
406+
!queryParams.endBeforeSet_;
420407
}
421408
if (queryParams.limitSet_) {
422409
obj[WIRE_PROTOCOL_CONSTANTS.LIMIT] = queryParams.limit_;

packages/database/src/core/view/filter/LimitedFilter.ts

+38-27
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,17 @@ export class LimitedFilter implements NodeFilter {
4646

4747
private readonly reverse_: boolean;
4848

49+
private readonly startIsInclusive_: boolean;
50+
51+
private readonly endIsInclusive_: boolean;
52+
4953
constructor(params: QueryParams) {
5054
this.rangedFilter_ = new RangedFilter(params);
5155
this.index_ = params.getIndex();
5256
this.limit_ = params.getLimit();
5357
this.reverse_ = !params.isViewFromLeft();
58+
this.startIsInclusive_ = !params.startAfterSet_;
59+
this.endIsInclusive_ = !params.endBeforeSet_;
5460
}
5561
updateChild(
5662
snap: Node,
@@ -119,20 +125,15 @@ export class LimitedFilter implements NodeFilter {
119125
let count = 0;
120126
while (iterator.hasNext() && count < this.limit_) {
121127
const next = iterator.getNext();
122-
let inRange;
123-
if (this.reverse_) {
124-
inRange =
125-
this.index_.compare(this.rangedFilter_.getStartPost(), next) <= 0;
128+
if (!this.withinDirectionalStart(next)) {
129+
// if we have not reached the start, skip to the next element
130+
continue;
131+
} else if (!this.withinDirectionalEnd(next)) {
132+
// if we have reached the end, stop adding elements
133+
break;
126134
} else {
127-
inRange =
128-
this.index_.compare(next, this.rangedFilter_.getEndPost()) <= 0;
129-
}
130-
if (inRange) {
131135
filtered = filtered.updateImmediateChild(next.name, next.node);
132136
count++;
133-
} else {
134-
// if we have reached the end post, we cannot keep adding elemments
135-
break;
136137
}
137138
}
138139
} else {
@@ -142,33 +143,21 @@ export class LimitedFilter implements NodeFilter {
142143
filtered = filtered.updatePriority(
143144
ChildrenNode.EMPTY_NODE
144145
) as ChildrenNode;
145-
let startPost;
146-
let endPost;
147-
let cmp;
146+
148147
let iterator;
149148
if (this.reverse_) {
150149
iterator = filtered.getReverseIterator(this.index_);
151-
startPost = this.rangedFilter_.getEndPost();
152-
endPost = this.rangedFilter_.getStartPost();
153-
const indexCompare = this.index_.getCompare();
154-
cmp = (a: NamedNode, b: NamedNode) => indexCompare(b, a);
155150
} else {
156151
iterator = filtered.getIterator(this.index_);
157-
startPost = this.rangedFilter_.getStartPost();
158-
endPost = this.rangedFilter_.getEndPost();
159-
cmp = this.index_.getCompare();
160152
}
161153

162154
let count = 0;
163-
let foundStartPost = false;
164155
while (iterator.hasNext()) {
165156
const next = iterator.getNext();
166-
if (!foundStartPost && cmp(startPost, next) <= 0) {
167-
// start adding
168-
foundStartPost = true;
169-
}
170157
const inRange =
171-
foundStartPost && count < this.limit_ && cmp(next, endPost) <= 0;
158+
count < this.limit_ &&
159+
this.withinDirectionalStart(next) &&
160+
this.withinDirectionalEnd(next);
172161
if (inRange) {
173162
count++;
174163
} else {
@@ -300,4 +289,26 @@ export class LimitedFilter implements NodeFilter {
300289
return snap;
301290
}
302291
}
292+
293+
private withinDirectionalStart = (node: NamedNode) =>
294+
this.reverse_ ? this.withinEndPost(node) : this.withinStartPost(node);
295+
296+
private withinDirectionalEnd = (node: NamedNode) =>
297+
this.reverse_ ? this.withinStartPost(node) : this.withinEndPost(node);
298+
299+
private withinStartPost = (node: NamedNode) => {
300+
const compareRes = this.index_.compare(
301+
this.rangedFilter_.getStartPost(),
302+
node
303+
);
304+
return this.startIsInclusive_ ? compareRes <= 0 : compareRes < 0;
305+
};
306+
307+
private withinEndPost = (node: NamedNode) => {
308+
const compareRes = this.index_.compare(
309+
node,
310+
this.rangedFilter_.getEndPost()
311+
);
312+
return this.endIsInclusive_ ? compareRes <= 0 : compareRes < 0;
313+
};
303314
}

0 commit comments

Comments
 (0)