@graffy/common
Version:
Common libraries that used by various Graffy modules.
137 lines (128 loc) • 4.07 kB
JavaScript
import {
isBranch,
isRange,
isLink,
isOlder,
getIndex,
getLastIndex,
} from '../node';
import { keyAfter, keyBefore } from './step';
import { wrap } from '../path';
import merge from './merge';
import add from './add';
class Result {
constructor(root) {
// When linked queries are added, they are forwarded to the root.
this.root = root || this;
}
addKnown(node) {
this.known = this.known || [];
merge(this.known, [node]);
}
addUnknown(node) {
this.unknown = this.unknown || [];
this.unknown.push(node);
}
addLinked(children) {
if (this.root !== this) return this.root.addLinked(children);
this.linked = this.linked || [];
add(this.linked, children);
}
}
export default function slice(graph, query, root) {
let result = new Result(root);
let currentQuery = query;
while (currentQuery) {
let index = 0;
for (const queryNode of currentQuery) {
if (isRange(queryNode)) {
sliceRange(graph, queryNode, result);
} else {
const key = queryNode.key;
index = getIndex(graph, key, index);
// console.log('Index', graph, key, index);
sliceNode(graph[index], queryNode, result);
}
}
currentQuery = root ? undefined : result.linked;
delete result.linked;
}
delete result.root;
return result;
}
function sliceNode(graph, query, result) {
const { key, version } = query;
const { root } = result;
// console.log('Slicing', graph, query);
if (!graph || graph.key > key || isOlder(graph, version)) {
// The node found doesn't match the query or it's too old.
result.addUnknown(query);
} else if (isRange(graph)) {
// The graph is indicating that this value was deleted.
if (isBranch(query)) {
const { known } = slice(
[{ key: '', end: '\uffff', version: graph.version }],
query.children,
);
result.addKnown({ key, version: graph.version, children: known });
} else {
result.addKnown({ key, end: key, version: graph.version });
}
} else if (isBranch(graph) && isBranch(query)) {
// Both sides are branches; recurse into them.
const { known, unknown } = slice(graph.children, query.children, root);
if (known) result.addKnown({ ...graph, children: known });
if (unknown) result.addUnknown({ ...query, children: unknown });
} else if (isLink(graph) && isBranch(query)) {
result.addKnown(graph);
result.addLinked(wrap(query.children, graph.path, version));
} else if (isBranch(graph) || isBranch(query)) {
// One side is a branch while the other is a leaf; throw error.
throw new Error('slice.leaf_branch_mismatch');
} else if (isRange(graph)) {
result.addKnown({ key, end: key, version: graph.version });
} else {
result.addKnown(graph);
}
}
export function sliceRange(graph, query, result) {
let { key, end, count, version } = query;
if (count > 0) {
for (let i = getIndex(graph, key); key <= end && count > 0; i++) {
const node = graph[i];
if (!node || key < node.key || isOlder(node, version)) break;
if (isRange(node)) {
result.addKnown(getOverlap(node, key, end));
} else {
sliceNode(node, { ...query, key }, result);
count--;
}
key = keyAfter(node.end || node.key);
}
} else {
for (let i = getLastIndex(graph, end) - 1; end >= key && count < 0; i--) {
const node = graph[i];
if (!node || end > (node.end || node.key) || isOlder(node, version))
break;
if (isRange(node)) {
result.addKnown(getOverlap(node, key, end));
} else {
sliceNode(node, { ...query, key: end }, result);
count++;
}
end = keyBefore(node.key);
}
}
if (count && key < end) {
const unknown = { ...query, key, end, count };
result.addUnknown(unknown);
}
}
function getOverlap(node, key, end) {
if (node.key >= key && node.end <= end) return node;
return {
...node,
key: node.key > key ? node.key : key,
end: node.end < end ? node.end : end,
};
}