jinaga
Version:
Data management for web and mobile applications.
208 lines • 8.96 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.skeletonOfSpecification = exports.emptySkeleton = void 0;
exports.emptySkeleton = {
facts: [],
inputs: [],
edges: [],
notExistsConditions: [],
outputs: []
};
function withFact(skeleton, factType) {
const factIndex = skeleton.facts.length + 1;
const fact = {
factIndex,
factType
};
skeleton = Object.assign(Object.assign({}, skeleton), { facts: [...skeleton.facts, fact] });
return { skeleton, factIndex };
}
function withInput(skeleton, factName, factType, inputIndex) {
const { skeleton: skeletonWithFact, factIndex } = withFact(skeleton, factType);
const input = {
factIndex,
inputIndex
};
return Object.assign(Object.assign({}, skeletonWithFact), { inputs: [...skeletonWithFact.inputs, input] });
}
function withEdge(skeleton, predecessorFactIndex, successorFactIndex, roleName, path) {
const edgeIndex = skeleton.edges.length + countEdges(skeleton.notExistsConditions) + 1;
const edge = {
edgeIndex,
predecessorFactIndex,
successorFactIndex,
roleName
};
if (path.length === 0) {
return Object.assign(Object.assign({}, skeleton), { edges: [...skeleton.edges, edge] });
}
else {
const notExistsConditions = notExistsWithEdge(skeleton.notExistsConditions, edge, path);
return Object.assign(Object.assign({}, skeleton), { notExistsConditions });
}
}
function withOutput(skeleton, factIndex) {
const output = {
factIndex
};
return Object.assign(Object.assign({}, skeleton), { outputs: [...skeleton.outputs, output] });
}
function withNotExistsCondition(skeleton, path) {
const { notExistsConditions: newNotExistsConditions, path: newPath } = notExistsWithCondition(skeleton.notExistsConditions, path);
const newSkeleton = Object.assign(Object.assign({}, skeleton), { notExistsConditions: newNotExistsConditions });
return { skeleton: newSkeleton, path: newPath };
}
function notExistsWithEdge(notExistsConditions, edge, path) {
if (path.length === 1) {
return notExistsConditions.map((c, i) => i === path[0] ?
{
edges: [...c.edges, edge],
notExistsConditions: c.notExistsConditions
} :
c);
}
else {
return notExistsConditions.map((c, i) => i === path[0] ?
{
edges: c.edges,
notExistsConditions: notExistsWithEdge(c.notExistsConditions, edge, path.slice(1))
} :
c);
}
}
function notExistsWithCondition(notExistsConditions, path) {
if (path.length === 0) {
path = [notExistsConditions.length];
notExistsConditions = [
...notExistsConditions,
{
edges: [],
notExistsConditions: []
}
];
return { notExistsConditions, path };
}
else {
const { notExistsConditions: newNotExistsConditions, path: newPath } = notExistsWithCondition(notExistsConditions[path[0]].notExistsConditions, path.slice(1));
notExistsConditions = notExistsConditions.map((c, i) => i === path[0] ?
{
edges: c.edges,
notExistsConditions: newNotExistsConditions
} :
c);
path = [path[0], ...newPath];
return { notExistsConditions, path };
}
}
function countEdges(notExistsConditions) {
return notExistsConditions.reduce((count, c) => count + c.edges.length + countEdges(c.notExistsConditions), 0);
}
function skeletonOfSpecification(specification) {
const givenFacts = specification.given.reduce((acc, label, i) => (Object.assign(Object.assign({}, acc), { [label.name]: {
type: label.type,
inputIndex: i
} })), {});
const { skeleton } = addEdges(exports.emptySkeleton, givenFacts, {}, [], specification.matches);
return skeleton;
}
exports.skeletonOfSpecification = skeletonOfSpecification;
function addEdges(skeleton, givenFacts, knownFacts, path, matches) {
for (const match of matches) {
for (const condition of match.conditions) {
if (condition.type === "path") {
({ skeleton, knownFacts } = addPathCondition(skeleton, givenFacts, knownFacts, path, match.unknown, condition));
}
else if (condition.type === "existential") {
if (condition.exists) {
// Include the edges of the existential condition into the current skeleton.
const { skeleton: newSkeleton } = addEdges(skeleton, givenFacts, knownFacts, path, condition.matches);
skeleton = newSkeleton;
}
else {
// Apply the where clause and continue with the tuple where it is true.
const { skeleton: skeletonWithNotExist, path: conditionalPath } = withNotExistsCondition(skeleton, path);
const { skeleton: newSkeletonWithNotExists } = addEdges(skeletonWithNotExist, givenFacts, knownFacts, conditionalPath, condition.matches);
skeleton = newSkeletonWithNotExists;
}
}
}
}
return { skeleton, knownFacts };
}
function addPathCondition(skeleton, givenFacts, knownFacts, path, unknown, condition) {
const given = givenFacts[condition.labelRight];
if (given) {
// If the right-hand side is a given, and not yet a known fact,
// then add it to the feed.
if (!knownFacts[condition.labelRight]) {
skeleton = withInput(skeleton, condition.labelRight, given.type, given.inputIndex);
knownFacts = Object.assign(Object.assign({}, knownFacts), { [condition.labelRight]: {
factIndex: skeleton.facts.length,
factType: given.type
} });
}
}
// Determine whether we have already written the output.
const knownFact = knownFacts[unknown.name];
const roleCount = condition.rolesLeft.length + condition.rolesRight.length;
// Walk up the right-hand side.
// This generates predecessor joins from a given or prior label.
const fact = knownFacts[condition.labelRight];
if (!fact) {
throw new Error(`Label ${condition.labelRight} not found. Known labels: ${Object.keys(knownFacts).join(", ")}`);
}
let factType = fact.factType;
let factIndex = fact.factIndex;
for (let i = 0; i < condition.rolesRight.length; i++) {
const role = condition.rolesRight[i];
if (i === roleCount - 1 && knownFact) {
// If we have already written the output, we can use the fact index.
skeleton = withEdge(skeleton, knownFact.factIndex, factIndex, role.name, path);
factIndex = knownFact.factIndex;
}
else {
// If we have not written the fact, we need to write it now.
const { skeleton: skeletonWithFact, factIndex: predecessorFactIndex } = withFact(skeleton, role.predecessorType);
skeleton = withEdge(skeletonWithFact, predecessorFactIndex, factIndex, role.name, path);
factIndex = predecessorFactIndex;
}
factType = role.predecessorType;
}
const rightType = factType;
// Walk up the left-hand side.
// We will need to reverse this walk to generate successor joins.
factType = unknown.type;
const newEdges = [];
for (const role of condition.rolesLeft) {
newEdges.push({
roleName: role.name,
successorType: factType
});
factType = role.predecessorType;
}
if (factType !== rightType) {
throw new Error(`Type mismatch: ${factType} is compared to ${rightType}`);
}
newEdges.reverse().forEach(({ roleName, successorType }, i) => {
if (condition.rolesRight.length + i === roleCount - 1 && knownFact) {
skeleton = withEdge(skeleton, factIndex, knownFact.factIndex, roleName, path);
factIndex = knownFact.factIndex;
}
else {
const { skeleton: skeletonWithFact, factIndex: successorFactIndex } = withFact(skeleton, successorType);
skeleton = withEdge(skeletonWithFact, factIndex, successorFactIndex, roleName, path);
factIndex = successorFactIndex;
}
});
// If we have not captured the known fact, add it now.
if (!knownFact) {
knownFacts = Object.assign(Object.assign({}, knownFacts), { [unknown.name]: { factIndex, factType: unknown.type } });
// If we have not written the output, write it now.
// Only write the output if we are not inside of an existential condition.
if (path.length === 0) {
skeleton = withOutput(skeleton, factIndex);
}
}
return { skeleton, knownFacts };
}
//# sourceMappingURL=skeleton.js.map