Skip to content
This repository was archived by the owner on Jan 19, 2024. It is now read-only.

Commit 16d9cb7

Browse files
committed
fix: various fixes to new rewrite
1 parent 67b66e9 commit 16d9cb7

File tree

8 files changed

+243
-56
lines changed

8 files changed

+243
-56
lines changed

PROTOCOL.md renamed to docs/DESIGN_GENERAL_NOTES.md

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
Protocol
2-
========
1+
# Query-Tree Negotiation
32

43
When we initially connect to the server, we build a snapshot of the tree and send it to the server. Following, every update to the tree gets transmitted to the server as queries are applied to the store.
54

@@ -124,3 +123,44 @@ query myQuery($a: Int!) {
124123
```
125124

126125
The server can be given variable values at the same time as query tree additions. However, the server will forget any variables that aren't relevant to the current query. This way the server keeps only the data it needs in memory with minimal messaging overhead and de-duplicated variable values.
126+
127+
# Distributing data to queries efficiently
128+
129+
Issue: we have a result tree that looks like:
130+
131+
```
132+
0: {
133+
1: {
134+
2: "John",
135+
3: "Doe",
136+
4: 28
137+
5: [153, 148, 140, 133]
138+
}
139+
}
140+
```
141+
142+
The array in qnode 5 needs to be shared between the queries. We can detect when we have reached a leaf when there are no child query nodes.
143+
144+
Here we have the issue. Cursors often extend into the values, past the qnode child leaves. We need to detach the cursors in the queries when reaching a value node.
145+
146+
- Cursors will need to build a "path so far" array (unfortunately).
147+
- This is necessary for caching.
148+
- Array of tuples, with `[Kind, (ArrayIndex/QueryNodeID), ValueKind]`
149+
- Examples (note that "value" in these points to location, is not a copy):
150+
- `ResultTree: {1: {2: [6123, 1523]}}`
151+
- Sequence:
152+
- `[Kind.QNodeID, 1, ValueKind.Object]`
153+
- `[Kind.QNodeID, 2, ValueKind.Value, [6123, 1623]]`
154+
- `ResultTree: {1: [{2: "test"}, {2: "what"}]}`
155+
- Sequence:
156+
- `[Kind.QNodeID, 1, ValueKind.Array]`
157+
- `[Kind.ArrayIndex, 0, ValueKind.Object]`
158+
- When reaching a value, drop the path so far and result tree.
159+
- Allow passing a map of query ID -> location pointers to a result tree cursor.
160+
- When adding a new query:
161+
- Iterate `DFS` over the current known result tree, applying values to the result.
162+
- Iterate over all known cursors, and add query by:
163+
- If the path sequence terminates in a Value then ignore cursor.
164+
- Iterate over path sequence, updating location.
165+
- When removing a query:
166+
- Iterate over all known cursors, remove query ID from map.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
},
1212
"dependencies": {
1313
"graphql": "^0.9.0",
14-
"lodash": "^4.17.0",
15-
"lru_map": "^0.3.3",
16-
"rgraphql": "^0.3.0",
14+
"lodash": "^4.0.0",
15+
"lru_map": "^0.3.0",
16+
"rgraphql": "^0.4.0",
1717
"rxjs": "^5.0.0"
1818
},
1919
"scripts": {

src/query-tree/query-tree.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export class QueryTreeNode {
6363

6464
public rootNodeMap?: { [id: number]: QueryTreeNode } = {};
6565
public variableStore?: VariableStore;
66+
public rootDisposeSubject?: Subject<QueryTreeNode>;
6667

6768
// If this query node is invalid, we will emit an error.
6869
// This is closed when we dispose this query tree node.
@@ -118,6 +119,7 @@ export class QueryTreeNode {
118119
this.variableStore.newVariables.subscribe((nvar: Variable) => {
119120
this.newVariables.push(nvar);
120121
});
122+
this.rootDisposeSubject = new Subject<QueryTreeNode>();
121123
}
122124
}
123125

@@ -372,6 +374,7 @@ export class QueryTreeNode {
372374
}
373375

374376
public garbageCollect(): boolean {
377+
let keepThis = Object.keys(this.queries).length > 0;
375378
if (this.gcNext) {
376379
this.gcNext = false;
377380
// let ir = this.isRoot;
@@ -381,13 +384,16 @@ export class QueryTreeNode {
381384
if (keep) {
382385
nchildren.push(<any>child);
383386
} else {
387+
if (keepThis) {
388+
this.root.rootDisposeSubject.next(child);
389+
}
384390
child.dispose();
385391
}
386392
}
387393
this.children = nchildren;
388394
}
389395

390-
return Object.keys(this.queries).length > 0;
396+
return keepThis;
391397
}
392398

393399
public propagateGcNext() {

src/result/result-tree.spec.ts

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,18 @@ import {
1818
Query,
1919
} from '../query-tree/query';
2020

21+
function parseQuery(query: string): any {
22+
return parse(`query { ${query} }`).definitions[0];
23+
}
24+
2125
describe('ResultTreeCursor', () => {
2226
it('should handle a root-level object value', () => {
2327
let root = {};
2428
let cursor = new ResultTreeCursor();
2529
cursor.path = [];
2630
cursor.resultLocations = {0: [root, (id) => {}]};
31+
cursor.queryNode = new QueryTreeNode();
32+
cursor.queryNode.buildQuery(parseQuery(`people { age }`), {});
2733
cursor.apply({queryNodeId: 1});
2834
cursor.apply({queryNodeId: 2, value: {intValue: 1, kind: Kind.PRIMITIVE_KIND_INT}});
2935
expect(root).toEqual({1: {2: 1}});
@@ -32,6 +38,8 @@ describe('ResultTreeCursor', () => {
3238
let root = {};
3339
let cursor = new ResultTreeCursor();
3440
cursor.path = [];
41+
cursor.queryNode = new QueryTreeNode();
42+
cursor.queryNode.buildQuery(parseQuery(`distances`), {});
3543
cursor.resultLocations = {0: [root, (id) => {}]};
3644
cursor.apply({queryNodeId: 1});
3745
cursor.apply({arrayIndex: 1, value: {intValue: 1, kind: Kind.PRIMITIVE_KIND_INT}});
@@ -41,6 +49,8 @@ describe('ResultTreeCursor', () => {
4149
let root = {};
4250
let cursor = new ResultTreeCursor();
4351
cursor.path = [];
52+
cursor.queryNode = new QueryTreeNode();
53+
cursor.queryNode.buildQuery(parseQuery(`people { name, age }`), {});
4454
cursor.resultLocations = {0: [root, (id) => {}]};
4555
cursor.apply({queryNodeId: 1});
4656
cursor.apply({arrayIndex: 1}); // checkpoint here.
@@ -57,6 +67,8 @@ describe('ResultTreeCursor', () => {
5767
let root = {};
5868
let cursor = new ResultTreeCursor();
5969
cursor.path = [];
70+
cursor.queryNode = new QueryTreeNode();
71+
cursor.queryNode.buildQuery(parseQuery(`ages`), {});
6072
cursor.resultLocations = {0: [root, (id) => {}]};
6173
cursor.apply({queryNodeId: 1});
6274
cursor.apply({arrayIndex: 1, value: {kind: Kind.PRIMITIVE_KIND_INT, intValue: 5}});
@@ -70,32 +82,30 @@ describe('ResultTreeCursor', () => {
7082
});
7183

7284
describe('ResultTree', () => {
73-
it('should handle a complex object', () => {
85+
it('should handle a complex object', (done) => {
7486
let qt = new QueryTreeNode();
75-
let q: Query = qt.buildQuery(<any>parse(`
76-
query {
77-
allPeople {
78-
name
79-
age
80-
parents
81-
}
82-
}
83-
`).definitions[0], {});
87+
let q: Query = qt.buildQuery(parseQuery(`allPeople { name, age, parents, favoriteMonths }`), {});
8488
let rt = new ResultTree(0, qt, CacheStrategy.CACHE_LRU, 2);
8589
let qres = rt.addQuery(q.id, (id) => {});
8690
let segments: IRGQLValue[] = [
8791
{queryNodeId: 1},
88-
{arrayIndex: 1},
92+
{arrayIndex: 1, posIdentifier: 1},
93+
{queryNodeId: 2, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'Jane'}},
94+
{queryNodeId: 5, posIdentifier: 1},
95+
{arrayIndex: 2, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'March'}},
96+
{queryNodeId: 3, posIdentifier: 1, value: {kind: Kind.PRIMITIVE_KIND_INT, intValue: 5}},
97+
{queryNodeId: 5, posIdentifier: 1},
98+
{arrayIndex: 1, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'January'}},
99+
{queryNodeId: 5, posIdentifier: 1},
100+
{arrayIndex: 4, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'December'}},
101+
{queryNodeId: 5, posIdentifier: 1},
102+
{arrayIndex: 3, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'August'}},
89103
{queryNodeId: 4, posIdentifier: 1},
90104
{arrayIndex: 3, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'Parent3'}},
91-
{posIdentifier: 1, arrayIndex: 1, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'Parent1'}},
92-
{posIdentifier: 1, arrayIndex: 2, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'Parent2'}},
93-
{queryNodeId: 1},
94-
{arrayIndex: 1},
95-
{queryNodeId: 3, value: {kind: Kind.PRIMITIVE_KIND_INT, intValue: 5}},
96-
{queryNodeId: 1},
97-
{arrayIndex: 1},
98-
{queryNodeId: 2, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'Jane'}},
105+
{queryNodeId: 4, posIdentifier: 1},
106+
{arrayIndex: 1, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'Parent1'}},
107+
{queryNodeId: 4, posIdentifier: 1},
108+
{arrayIndex: 2, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'Parent2'}},
99109
];
100110

101111
for (let seg of segments) {
@@ -107,17 +117,20 @@ query {
107117
2: 'Jane',
108118
3: 5,
109119
4: ['Parent1', 'Parent2', 'Parent3'],
120+
5: ['January', 'March', 'August', 'December'],
110121
}],
111122
});
112123
expect(qres).toEqual({
113124
'allPeople': [{
114125
'name': 'Jane',
115126
'age': 5,
116127
'parents': ['Parent1', 'Parent2', 'Parent3'],
128+
'favoriteMonths': ['January', 'March', 'August', 'December'],
117129
}],
118130
});
119131

120132
// Attempt to build a second query to read the cache.
133+
let qorig = q;
121134
q = qt.buildQuery(<any>parse(`
122135
query {
123136
allPeople {
@@ -131,5 +144,30 @@ query {
131144
'parents': ['Parent1', 'Parent2', 'Parent3'],
132145
}],
133146
});
147+
148+
rt.handleSegment({queryNodeId: 4, posIdentifier: 1});
149+
rt.handleSegment({arrayIndex: 4, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'Parent4'}});
150+
151+
expect(qres).toEqual({
152+
'allPeople': [{
153+
'parents': ['Parent1', 'Parent2', 'Parent3', 'Parent4'],
154+
}],
155+
});
156+
157+
// Remove the original query
158+
qorig.unsubscribe();
159+
160+
// Force immediate gc
161+
qt.garbageCollect();
162+
163+
// Make sure the data was removed from the tree
164+
setTimeout(() => {
165+
expect(rt.result).toEqual({
166+
1: [{
167+
4: ['Parent1', 'Parent2', 'Parent3', 'Parent4'],
168+
}],
169+
});
170+
done();
171+
}, 300);
134172
});
135173
});

0 commit comments

Comments
 (0)