[go: nahoru, domu]

Skip to content
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

Add an interface to ServerValues that lets us read synctrees just in time #2499

Merged
merged 5 commits into from
Mar 30, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add an interface to ServerValues that lets us read synctrees just in …
…time
  • Loading branch information
inlined committed Mar 23, 2020
commit ece6c5cfc94aa9aba6eb755f57e7e1e63c38524d
103 changes: 82 additions & 21 deletions packages/database/src/core/util/ServerValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,53 @@ import { ChildrenNode } from '../snap/ChildrenNode';
import { SyncTree } from '../SyncTree';
import { Indexable } from './misc';

/* It's critical for performance that we not calculate actual values from a SyncTree
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/not/do not/

* unless and until the value is needed. Because we expose both a SyncTree and Node
* version of deferred value resolution, we ned a wrapper class that will let us share
* code.
*
* @see https://github.com/firebase/firebase-js-sdk/issues/2487
*/
interface DeferredExistingValue {
getImmediateChild(childName: string): DeferredExistingValue;
node(): Node;
}

class ExistingSnapshotValue implements DeferredExistingValue {
private node_: Node;
constructor(node: Node) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: You can drop the suffix for members of classes that are not part of the public API, which will allow you to use constructor property assignments (constructor(readaonly node:Node)).

Renaming to node also allows you to get rid of the getter here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I do that I fail to implement the protocol because node is a value instead of a method.

this.node_ = node;
}

getImmediateChild(childName: string): DeferredExistingValue {
const child = this.node_.getImmediateChild(childName);
return new ExistingSnapshotValue(child);
}

node(): Node {
return this.node_;
}
}

class DeferredSyncTreeValue implements DeferredExistingValue {
private syncTree_: SyncTree;
private path_: Path;

constructor(syncTree: SyncTree, path: Path) {
this.syncTree_ = syncTree;
this.path_ = path;
}

getImmediateChild(childName: string): DeferredExistingValue {
const childPath = this.path_.child(childName);
return new DeferredSyncTreeValue(this.syncTree_, childPath);
}

node(): Node {
return this.syncTree_.calcCompleteEventCache(this.path_);
}
}

/**
* Generate placeholders for deferred values.
* @param {?Object} values
Expand All @@ -48,9 +95,9 @@ export const generateWithValues = function(
* @param {!Object} serverValues
* @return {!(string|number|boolean)}
*/
export const resolveDeferredValue = function(
export const resolveDeferredLeafValue = function(
value: { [k: string]: unknown } | string | number | boolean,
existing: Node,
existingVal: DeferredExistingValue,
serverValues: { [k: string]: unknown }
): string | number | boolean {
if (!value || typeof value !== 'object') {
Expand All @@ -59,17 +106,17 @@ export const resolveDeferredValue = function(
assert('.sv' in value, 'Unexpected leaf node or priority contents');

if (typeof value['.sv'] === 'string') {
return resolveScalarDeferredValue(value['.sv'], existing, serverValues);
return resolveScalarDeferredValue(value['.sv'], existingVal, serverValues);
} else if (typeof value['.sv'] === 'object') {
return resolveComplexDeferredValue(value['.sv'], existing, serverValues);
return resolveComplexDeferredValue(value['.sv'], existingVal, serverValues);
} else {
assert(false, 'Unexpected server value: ' + JSON.stringify(value, null, 2));
}
};

const resolveScalarDeferredValue = function(
op: string,
existing: Node,
existing: DeferredExistingValue,
serverValues: { [k: string]: unknown }
): string | number | boolean {
switch (op) {
Expand All @@ -82,7 +129,7 @@ const resolveScalarDeferredValue = function(

const resolveComplexDeferredValue = function(
op: object,
existing: Node,
existing: DeferredExistingValue,
unused: { [k: string]: unknown }
): string | number | boolean {
if (!op.hasOwnProperty('increment')) {
Expand All @@ -93,12 +140,18 @@ const resolveComplexDeferredValue = function(
assert(false, 'Unexpected increment value: ' + delta);
}

const existingNode = existing.node();
assert(
existingNode !== null && typeof existingNode !== 'undefined',
'Expected ChildrenNode.EMPTY_NODE for nulls'
);

// Incrementing a non-number sets the value to the incremented amount
if (!existing.isLeafNode()) {
if (!existingNode.isLeafNode()) {
return delta;
}

const leaf = existing as LeafNode;
const leaf = existingNode as LeafNode;
const existingVal = leaf.getValue();
if (typeof existingVal !== 'number') {
return delta;
Expand All @@ -122,14 +175,10 @@ export const resolveDeferredValueTree = function(
): SparseSnapshotTree {
const resolvedTree = new SparseSnapshotTree();
tree.forEachTree(new Path(''), (path, node) => {
const existing = syncTree.calcCompleteEventCache(path);
assert(
existing !== null && typeof existing !== 'undefined',
'Expected ChildrenNode.EMPTY_NODE for nulls'
);
const deferredExisting = new DeferredSyncTreeValue(syncTree, path);
resolvedTree.remember(
path,
resolveDeferredValueSnapshot(node, existing, serverValues)
resolveDeferredValue(node, deferredExisting, serverValues)
);
});
return resolvedTree;
Expand All @@ -147,25 +196,37 @@ export const resolveDeferredValueSnapshot = function(
node: Node,
existing: Node,
serverValues: Indexable
): Node {
return resolveDeferredValue(
node,
new ExistingSnapshotValue(existing),
serverValues
);
};

function resolveDeferredValue(
node: Node,
existingVal: DeferredExistingValue,
serverValues: Indexable
): Node {
const rawPri = node.getPriority().val() as
| Indexable
| boolean
| null
| number
| string;
const priority = resolveDeferredValue(
const priority = resolveDeferredLeafValue(
rawPri,
existing.getPriority(),
existingVal.getImmediateChild('.priority'),
serverValues
);
let newNode: Node;

if (node.isLeafNode()) {
const leafNode = node as LeafNode;
const value = resolveDeferredValue(
const value = resolveDeferredLeafValue(
leafNode.getValue(),
existing,
existingVal,
serverValues
);
if (
Expand All @@ -183,9 +244,9 @@ export const resolveDeferredValueSnapshot = function(
newNode = newNode.updatePriority(new LeafNode(priority));
}
childrenNode.forEachChild(PRIORITY_INDEX, (childName, childNode) => {
const newChildNode = resolveDeferredValueSnapshot(
const newChildNode = resolveDeferredValue(
childNode,
existing.getImmediateChild(childName),
existingVal.getImmediateChild(childName),
serverValues
);
if (newChildNode !== childNode) {
Expand All @@ -194,4 +255,4 @@ export const resolveDeferredValueSnapshot = function(
});
return newNode;
}
};
}