Skip to content

Commit 366cb79

Browse files
Add handling for single node (#68)
* Add handling for single node * making right node as optional * add tests for single node * review fix * tests fix * review fix
1 parent d88b703 commit 366cb79

File tree

7 files changed

+170
-20
lines changed

7 files changed

+170
-20
lines changed

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.72",
3+
"version": "0.0.73",
44
"dependencies": {
55
"@swc/helpers": "~0.5.0"
66
},

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ describe('Table schema functions', () => {
8787
});
8888

8989
it('should correctly generate a SQL query from the provided join path, table schema SQL map, and directed graph', () => {
90-
const joinPath = [
90+
const joinPaths = [
9191
[
9292
{ left: 'table1', right: 'table2', on: 'id' },
9393
{ left: 'table2', right: 'table3', on: 'id' },
@@ -103,7 +103,7 @@ describe('Table schema functions', () => {
103103
table3: 'select * from table3',
104104
};
105105
const sqlQuery = generateSqlQuery(
106-
joinPath,
106+
joinPaths,
107107
tableSchemaSqlMap,
108108
directedGraph
109109
);

meerkat-core/src/joins/joins.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { JoinEdge, Query, TableSchema } from '../types/cube-types';
1+
import { JoinPath, Query, TableSchema, isJoinNode } from '../types/cube-types';
22

33
export type Graph = {
44
[key: string]: { [key: string]: { [key: string]: string } };
55
};
66

77
export function generateSqlQuery(
8-
path: JoinEdge[][],
8+
path: JoinPath[],
99
tableSchemaSqlMap: { [key: string]: string },
1010
directedGraph: Graph
1111
): string {
@@ -18,6 +18,14 @@ export function generateSqlQuery(
1818
const startingNode = path[0][0].left;
1919
let query = `${tableSchemaSqlMap[startingNode]}`;
2020

21+
/**
22+
* If the starting node is not a join node, then return the query as is.
23+
* It means that the query is a single node query.
24+
*/
25+
if (!isJoinNode(path[0][0])) {
26+
return query;
27+
}
28+
2129
const visitedNodes = new Map();
2230

2331
for (let i = 0; i < path.length; i++) {
@@ -28,6 +36,11 @@ export function generateSqlQuery(
2836
}
2937
for (let j = 0; j < path[i].length; j++) {
3038
const currentEdge = path[i][j];
39+
40+
if (!isJoinNode(currentEdge)) {
41+
continue;
42+
}
43+
3144
const visitedFrom = visitedNodes.get(currentEdge.right);
3245

3346
// If node is already visited from the same edge, continue to next iteration
@@ -194,10 +207,8 @@ export const getCombinedTableSchema = async (
194207
throw new Error('A loop was detected in the joins.');
195208
}
196209

197-
console.log('directedGraph', directedGraph);
198-
199210
const baseSql = generateSqlQuery(
200-
cubeQuery.joinPath || [],
211+
cubeQuery.joinPaths || [],
201212
tableSchemaSqlMap,
202213
directedGraph
203214
);

meerkat-core/src/types/cube-types/query.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ interface QueryTimeDimension {
115115
* Join Edge data type.
116116
*/
117117

118-
interface JoinEdge {
118+
interface JoinNode {
119119
/**
120120
* Left node.
121121
*/
@@ -152,6 +152,23 @@ interface JoinEdge {
152152
*/
153153
}
154154

155+
/**
156+
* Single node data type.
157+
* This is the case when there is no join. Just a single node.
158+
*/
159+
interface SingleNode {
160+
/**
161+
* Left node.
162+
*/
163+
left: Member;
164+
}
165+
166+
type JoinPath = [JoinNode | SingleNode, ...JoinNode[]];
167+
168+
export const isJoinNode = (node: JoinNode | SingleNode): node is JoinNode => {
169+
return 'right' in node;
170+
};
171+
155172
/**
156173
* Incoming network query data type.
157174
*/
@@ -163,7 +180,7 @@ interface Query {
163180
dimensions?: (Member | TimeMember)[];
164181
filters?: MeerkatQueryFilter[];
165182
timeDimensions?: QueryTimeDimension[];
166-
joinPath?: JoinEdge[][];
183+
joinPaths?: JoinPath[];
167184
segments?: Member[];
168185
limit?: null | number;
169186
offset?: number;
@@ -196,7 +213,7 @@ export {
196213
ApiScopes,
197214
ApiType,
198215
FilterOperator,
199-
JoinEdge,
216+
JoinPath,
200217
LogicalAndFilter,
201218
LogicalOrFilter,
202219
MeerkatQueryFilter,

meerkat-core/src/utils/get-possible-nodes.spec.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ describe('Table schema functions', () => {
161161
],
162162
];
163163

164+
const singleNodeJoinPath = [
165+
[
166+
{
167+
left: 'node1',
168+
},
169+
],
170+
];
171+
164172
const intermediateJoinPath = [
165173
[
166174
{
@@ -222,6 +230,75 @@ describe('Table schema functions', () => {
222230
],
223231
];
224232

233+
it('Test single node join path', async () => {
234+
const nestedSchema = await getNestedTableSchema(
235+
tableSchema,
236+
singleNodeJoinPath,
237+
1
238+
);
239+
240+
expect(nestedSchema).toEqual({
241+
name: 'node1',
242+
measures: [],
243+
dimensions: [
244+
{
245+
schema: {
246+
name: 'id',
247+
sql: 'node1.id',
248+
},
249+
children: [
250+
{
251+
name: 'node2',
252+
measures: [],
253+
dimensions: [
254+
{
255+
schema: {
256+
name: 'id',
257+
sql: 'node2.id',
258+
},
259+
children: [],
260+
},
261+
{
262+
schema: {
263+
name: 'node11_id',
264+
sql: 'node2.node11_id',
265+
},
266+
children: [],
267+
},
268+
],
269+
},
270+
{
271+
name: 'node3',
272+
measures: [],
273+
dimensions: [
274+
{
275+
schema: {
276+
name: 'id',
277+
sql: 'node3.id',
278+
},
279+
children: [],
280+
},
281+
],
282+
},
283+
{
284+
name: 'node6',
285+
measures: [],
286+
dimensions: [
287+
{
288+
schema: {
289+
name: 'id',
290+
sql: 'node6.id',
291+
},
292+
children: [],
293+
},
294+
],
295+
},
296+
],
297+
},
298+
],
299+
});
300+
});
301+
225302
it('Test basic join path with depth 0 (should return original graph)', async () => {
226303
const nestedSchema = await getNestedTableSchema(
227304
tableSchema,

meerkat-core/src/utils/get-possible-nodes.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { Graph, checkLoopInGraph, createDirectedGraph } from '../joins/joins';
2-
import { Dimension, JoinEdge, Measure, TableSchema } from '../types/cube-types';
2+
import {
3+
Dimension,
4+
JoinPath,
5+
Measure,
6+
TableSchema,
7+
isJoinNode,
8+
} from '../types/cube-types';
39

410
export interface NestedMeasure {
511
schema: Measure;
@@ -19,7 +25,7 @@ export interface NestedTableSchema {
1925

2026
export const getNestedTableSchema = (
2127
tableSchemas: TableSchema[],
22-
joinPath: JoinEdge[][],
28+
joinPath: JoinPath[],
2329
depth: number
2430
) => {
2531
const tableSchemaSqlMap: { [key: string]: string } = {};
@@ -58,12 +64,22 @@ export const getNestedTableSchema = (
5864
const checkedPaths: { [key: string]: boolean } = {};
5965

6066
const buildNestedSchema = (
61-
edges: JoinEdge[],
67+
edges: JoinPath,
6268
index: number,
6369
nestedTableSchema: NestedTableSchema,
6470
tableSchemas: TableSchema[]
6571
): NestedTableSchema => {
6672
const edge = edges[index];
73+
74+
/**
75+
* If there is no right table, return the nested schema immediately
76+
* This means there is a single node in the path.
77+
*/
78+
79+
if (!isJoinNode(edge)) {
80+
return nestedTableSchema;
81+
}
82+
6783
// If the path has been checked before, return the nested schema immediately
6884
const pathKey = `${edge.left}-${edge.right}-${edge.on}`;
6985
if (checkedPaths[pathKey]) {
@@ -82,6 +98,10 @@ export const getNestedTableSchema = (
8298
(schema) => schema.name === edge.right
8399
) as TableSchema;
84100

101+
if (!rightSchema) {
102+
throw new Error(`The schema for ${edge.right} does not exist.`);
103+
}
104+
85105
// Mark the path as checked
86106
checkedPaths[pathKey] = true;
87107

meerkat-node/src/__tests__/joins.spec.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ describe('Joins Tests', () => {
308308
BOOK_SCHEMA.joins = [];
309309
const query = {
310310
measures: ['books.total_book_count', 'authors.total_author_count'],
311-
joinPath: [
311+
joinPaths: [
312312
[
313313
{
314314
left: 'authors',
@@ -340,6 +340,31 @@ describe('Joins Tests', () => {
340340
);
341341
});
342342

343+
it('Single node in the path', async () => {
344+
const query = {
345+
measures: [],
346+
filters: [],
347+
dimensions: ['orders.order_id'],
348+
joinPaths: [
349+
[
350+
{
351+
left: 'orders',
352+
on: 'order_id',
353+
},
354+
],
355+
],
356+
};
357+
const sql = await cubeQueryToSQL(query, [AUTHOR_SCHEMA, ORDER_SCHEMA]);
358+
console.info(`SQL for Simple Cube Query: `, sql);
359+
const output = await duckdbExec(sql);
360+
const parsedOutput = JSON.parse(JSON.stringify(output));
361+
console.info('parsedOutput', parsedOutput);
362+
expect(sql).toEqual(
363+
'SELECT orders__order_id FROM (SELECT *, orders.order_id AS orders__order_id FROM (select * from orders) AS orders) AS MEERKAT_GENERATED_TABLE GROUP BY orders__order_id'
364+
);
365+
expect(parsedOutput).toHaveLength(11);
366+
});
367+
343368
it('Three tables join - Direct', async () => {
344369
const DEMO_SCHEMA = structuredClone(ORDER_SCHEMA);
345370

@@ -351,7 +376,7 @@ describe('Joins Tests', () => {
351376

352377
const query = {
353378
measures: ['orders.total_order_amount'],
354-
joinPath: [
379+
joinPaths: [
355380
[
356381
{
357382
left: 'customers',
@@ -408,7 +433,7 @@ describe('Joins Tests', () => {
408433

409434
const query = {
410435
measures: ['orders.total_order_amount'],
411-
joinPath: [
436+
joinPaths: [
412437
[
413438
{
414439
left: 'customers',
@@ -465,7 +490,7 @@ describe('Joins Tests', () => {
465490
it('Joins with Different Paths', async () => {
466491
const query1 = {
467492
measures: ['orders.total_order_amount'],
468-
joinPath: [
493+
joinPaths: [
469494
[
470495
{
471496
left: 'customers',
@@ -512,7 +537,7 @@ describe('Joins Tests', () => {
512537

513538
const query2 = {
514539
measures: ['orders.total_order_amount'],
515-
joinPath: [
540+
joinPaths: [
516541
[
517542
{
518543
left: 'customers',
@@ -555,7 +580,7 @@ describe('Joins Tests', () => {
555580
it('Success Join with filters', async () => {
556581
const query = {
557582
measures: ['orders.total_order_amount'],
558-
joinPath: [
583+
joinPaths: [
559584
[
560585
{
561586
left: 'customers',

0 commit comments

Comments
 (0)