Skip to content

Commit 4a17236

Browse files
feat: Schema tree calculcation for circular input schemas (#79)
* circular path schema calcaulation * updated error message with input * removed type from fixture * test failure fix * added test cases for loop in join paths * bumped package verisons * lint error fix * removed uneeded code * test failure fix
1 parent 7d8b07f commit 4a17236

File tree

9 files changed

+1511
-1243
lines changed

9 files changed

+1511
-1243
lines changed

meerkat-browser/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devrev/meerkat-browser",
3-
"version": "0.0.72",
3+
"version": "0.0.73",
44
"dependencies": {
55
"@swc/helpers": "~0.5.0",
66
"@devrev/meerkat-core": "*",

meerkat-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devrev/meerkat-core",
3-
"version": "0.0.74",
3+
"version": "0.0.75",
44
"dependencies": {
55
"@swc/helpers": "~0.5.0"
66
},

meerkat-core/src/joins/joins.spec.ts

Lines changed: 31 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
checkLoopInGraph,
2+
checkLoopInJoinPath,
33
createDirectedGraph,
44
generateSqlQuery,
55
} from './joins';
@@ -60,32 +60,6 @@ describe('Table schema functions', () => {
6060
});
6161
});
6262

63-
it('should correctly identify if a loop exists in the graph', () => {
64-
const graph = {
65-
table1: {
66-
table2: {
67-
field1: 'table2.field3 = table1.field4',
68-
},
69-
table3: {
70-
field2: 'table3.field5 = table1.field2',
71-
},
72-
},
73-
table2: {
74-
table3: {
75-
field3: 'table3.field4 = table2.field3',
76-
},
77-
},
78-
table3: {
79-
table1: {
80-
field5: 'table1.field1 = table3.field2',
81-
},
82-
},
83-
};
84-
const hasLoop = checkLoopInGraph(graph);
85-
86-
expect(hasLoop).toBe(true);
87-
});
88-
8963
it('should correctly generate a SQL query from the provided join path, table schema SQL map, and directed graph', () => {
9064
const joinPaths = [
9165
[
@@ -113,21 +87,34 @@ describe('Table schema functions', () => {
11387
);
11488
});
11589

116-
it('should throw an error when a cycle exists in checkLoopInGraph', () => {
117-
const graph = {
118-
node1: { node2: { id: 'node1.id = node2.id' } },
119-
node2: { node3: { id: 'node2.id = node3.id ' } },
120-
node3: { node1: { id: 'node3.id = node1.id' } },
121-
};
122-
const output = checkLoopInGraph(graph);
123-
expect(output).toBe(true);
124-
});
125-
126-
it('checkLoopInGraph should return false for disconnected graph', () => {
127-
const graph = {
128-
node1: { node2: { id: 'node1.id = node2.id ' } },
129-
node3: { node4: { id: 'node3.id = node4.id ' } },
130-
};
131-
expect(checkLoopInGraph(graph)).toBe(false);
132-
});
90+
describe('checkLoopInJoinPath', () => {
91+
it('should return false if there is no loop in the join path', () => {
92+
const joinPath = [
93+
[
94+
{ left: 'table1', right: 'table2', on: 'id' },
95+
{ left: 'table2', right: 'table3', on: 'id' },
96+
],
97+
];
98+
expect(checkLoopInJoinPath(joinPath)).toBe(false);
99+
})
100+
it('should return true if there is a loop in the join path', () => {
101+
const joinPath = [
102+
[
103+
{ left: 'table1', right: 'table2', on: 'id' },
104+
{ left: 'table2', right: 'table3', on: 'id' },
105+
{ left: 'table3', right: 'table1', on: 'id' },
106+
],
107+
];
108+
expect(checkLoopInJoinPath(joinPath)).toBe(true);
109+
})
110+
it('should return false for single node', () => {
111+
const joinPath = [
112+
[
113+
{ left: 'table1', },
114+
{ left: 'table1' },
115+
],
116+
];
117+
expect(checkLoopInJoinPath(joinPath)).toBe(false);
118+
})
119+
})
133120
});

meerkat-core/src/joins/joins.ts

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,9 @@ export function generateSqlQuery(
5757
// If visitedFrom is undefined, this is the first visit to the node
5858
visitedNodes.set(currentEdge.right, currentEdge);
5959

60-
query += ` LEFT JOIN (${tableSchemaSqlMap[currentEdge.right]}) AS ${
61-
currentEdge.right
62-
} ON ${
63-
directedGraph[currentEdge.left][currentEdge.right][currentEdge.on]
64-
}`;
60+
query += ` LEFT JOIN (${tableSchemaSqlMap[currentEdge.right]}) AS ${currentEdge.right
61+
} ON ${directedGraph[currentEdge.left][currentEdge.right][currentEdge.on]
62+
}`;
6563
}
6664
}
6765

@@ -152,38 +150,23 @@ export const createDirectedGraph = (
152150
return directedGraph;
153151
};
154152

155-
function DFS(
156-
graph: any,
157-
node: string,
158-
visited: Set<string>,
159-
recStack: Set<string>
160-
): boolean {
161-
visited.add(node);
162-
recStack.add(node);
163-
164-
for (const neighbor in graph[node]) {
165-
if (!visited.has(neighbor) && DFS(graph, neighbor, visited, recStack)) {
166-
return true;
167-
} else if (recStack.has(neighbor)) {
168-
return true;
169-
}
170-
}
171-
172-
recStack.delete(node);
173-
return false;
174-
}
175153

176-
export function checkLoopInGraph(graph: any): boolean {
177-
const visited = new Set<string>();
178-
const recStack = new Set<string>();
179-
180-
for (const node in graph) {
181-
if (DFS(graph, node, visited, recStack)) {
182-
return true;
154+
export const checkLoopInJoinPath = (joinPath: JoinPath[]) => {
155+
for (let i = 0; i < joinPath.length; i++) {
156+
const visitedNodes = new Set<string>();
157+
const currentJoinPath = joinPath[i];
158+
visitedNodes.add(currentJoinPath[0].left);
159+
for (let j = 0; j < currentJoinPath.length; j++) {
160+
const currentEdge = currentJoinPath[j];
161+
if (isJoinNode(currentEdge) && visitedNodes.has(currentEdge.right)) {
162+
if (visitedNodes.has(currentEdge.right)) {
163+
return true;
164+
}
165+
visitedNodes.add(currentEdge.right);
166+
}
183167
}
184168
}
185-
186-
return false;
169+
return false
187170
}
188171

189172
export const getCombinedTableSchema = async (
@@ -202,9 +185,9 @@ export const getCombinedTableSchema = async (
202185
);
203186

204187
const directedGraph = createDirectedGraph(tableSchema, tableSchemaSqlMap);
205-
const hasLoop = checkLoopInGraph(directedGraph);
188+
const hasLoop = checkLoopInJoinPath(cubeQuery.joinPaths || []);
206189
if (hasLoop) {
207-
throw new Error('A loop was detected in the joins.');
190+
throw new Error(`A loop was detected in the joins. ${JSON.stringify(cubeQuery.joinPaths || [])}`);
208191
}
209192

210193
const baseSql = generateSqlQuery(

0 commit comments

Comments
 (0)