-
Notifications
You must be signed in to change notification settings - Fork 910
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Index Scanning #6008
Implement Index Scanning #6008
Conversation
|
36f4a5a
to
ff719f2
Compare
@@ -103,13 +103,12 @@ export interface IndexManager { | |||
|
|||
/** | |||
* Returns the documents that match the given target based on the provided | |||
* index. | |||
* index. Returns `null` if the target does not have a matching index. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is pending review on Android: firebase/firebase-android-sdk#3444
} | ||
|
||
/** Converts a binary string to a Base64 encoded string. */ | ||
export function encodeBase64(raw: string): string { | ||
return new Buffer(raw, 'binary').toString('base64'); | ||
return Buffer.from(raw, 'binary').toString('base64'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This creates a deprecation warning in Node.
Size Report 1Affected Products
Test Logs |
Size Analysis Report 1This report is too large (651,534 characters) to be displayed here in a GitHub comment. Please use the below link to see the full report on Google Cloud Storage.Test Logs |
d9a242d
to
7ccadd8
Compare
// that we would split an existing range into multiple ranges that exclude | ||
// the values from any notIn filter. | ||
|
||
// The first index range starts with the lower bound and ends at the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I understand this well..what if notInValues[0]
is not covered by the first range? What if it is < indexRange.lower?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was indeed completely broken, and also did not work correctly on Android. I ported the Android fix firebase/firebase-android-sdk#3454 and adopted it to work with the Web port.
ranges.push( | ||
IDBKeyRange.bound( | ||
indexRange.lower, | ||
this.generateNotInBound(indexRange.lower, barriers[0]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if barriers[0]
is larger than indexRange.upper
, is that not possible?
I think we could do something like:
// Assume barriers is sorted, it is right?
let runningLower = indexRange.lower
let runningOpen = indexRange.lowerOpen
for(const barrier: barriers) {
if(barrier <= indexRange.lower) {continue;}
if(barrier >= indexRange.upper) {break;}
ranges.push([runningLower /*need to handle lowerOpen as well*/, barrier]);
runningLower = barrier
lowerOpen = true
}
// Handles last
ranges.push([runningLower, indexRange.upper])
You could even do handle this by taking IDBKeyRange[]
, sort both indexRanges
and barriers
together..then process from left to right. But maybe let's not go there, it should be rare in real world scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Barriers are filtered both by the lower and the upper bound, so all barriers should fit between the range.
I have actually spent some time thinking about how to generalize the whole "barrier setup" so that I can treat IDBKeyRanges and barriers the same and only write one loop. I did not consider the approach that you outlined here, which solves some of the problems I had encountered while still reducing the lines of code. I updated the PR accordingly. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found a bunch of cases where this does not work. Will update soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated this code to work on browsers and Node. The browsers seem to do more validations, so I had to change some of the logic here. I also added a bunch of test code. Note that I myself don't fully grasp what is going on here anymore, but the tests are all passing and I think that I covered all corner cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still broken
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I rewrote this part of the PR in its entirety. It should be much cleaner now.
const bounds: IndexEntry[] = []; | ||
bounds.push(lower); | ||
for (const notInValue of notInValues) { | ||
const sortsAfter = indexEntryComparator(notInValue, lower); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe cmpToLower
and cmpToUpper
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I am not quite sold on either name, but using "cmp" helps a bit.
const sortsAfter = indexEntryComparator(notInValue, lower); | ||
const sortsBefore = indexEntryComparator(notInValue, upper); | ||
|
||
if (sortsAfter > 0 && sortsBefore < 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you feel about:
if (cmpToLower == 0) {
// Lower happens to be excluded by `notInValue`.
bounds[0] = lower.successor();
} else if (cmpToLower > 0 && cmpToUpper < 0) {
// `notInValue` is in the middle of the range
...
} else if(cmpToUpper == 0) {
// Upper happens to be excluded by `notInValue`.
} else {
// `notInValue` (and hence the following values) is out of the range
break;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Good suggestion with the break
statement.
* Maps from a target to its equivalent list of sub-targets. Each sub-target | ||
* contains only one term from the target's disjunctive normal form (DNF). | ||
*/ | ||
private targetToDnfSubTargets = new ObjectMap<Target, Target[]>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated to this PR: but SubTarget indicates SubQuery as well, and that term meant very different thing to most people.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure I fully understand this. This part of the code only deals with targets - and only the ones normalized for indexing, not the targets we send to the backend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point is maybe we can rename the term sub-target..but we can deal with this later.
* Maps from a target to its equivalent list of sub-targets. Each sub-target | ||
* contains only one term from the target's disjunctive normal form (DNF). | ||
*/ | ||
private targetToDnfSubTargets = new ObjectMap<Target, Target[]>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point is maybe we can rename the term sub-target..but we can deal with this later.
const bounds: IndexEntry[] = []; | ||
bounds.push(lower); | ||
for (const notInValue of notInValues) { | ||
const cpmToLower = indexEntryComparator(notInValue, lower); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
Changeset File Check
|
This adds all index evaluation logic to the Web SDK and ports all tests from Android. The query logic is mostly the same as Android, with the exception of notIn/notEquals queries which are executed by doing scans across split ranges.
Example: