Skip to content

Commit

Permalink
Add handling for single node (#68)
Browse files Browse the repository at this point in the history
* Add handling for single node

* making right node as optional

* add tests for single node

* review fix

* tests fix

* review fix
  • Loading branch information
Prabhat0602-source authored Feb 26, 2024
1 parent d88b703 commit 366cb79
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 20 deletions.
2 changes: 1 addition & 1 deletion meerkat-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devrev/meerkat-core",
"version": "0.0.72",
"version": "0.0.73",
"dependencies": {
"@swc/helpers": "~0.5.0"
},
Expand Down
4 changes: 2 additions & 2 deletions meerkat-core/src/joins/joins.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe('Table schema functions', () => {
});

it('should correctly generate a SQL query from the provided join path, table schema SQL map, and directed graph', () => {
const joinPath = [
const joinPaths = [
[
{ left: 'table1', right: 'table2', on: 'id' },
{ left: 'table2', right: 'table3', on: 'id' },
Expand All @@ -103,7 +103,7 @@ describe('Table schema functions', () => {
table3: 'select * from table3',
};
const sqlQuery = generateSqlQuery(
joinPath,
joinPaths,
tableSchemaSqlMap,
directedGraph
);
Expand Down
21 changes: 16 additions & 5 deletions meerkat-core/src/joins/joins.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { JoinEdge, Query, TableSchema } from '../types/cube-types';
import { JoinPath, Query, TableSchema, isJoinNode } from '../types/cube-types';

export type Graph = {
[key: string]: { [key: string]: { [key: string]: string } };
};

export function generateSqlQuery(
path: JoinEdge[][],
path: JoinPath[],
tableSchemaSqlMap: { [key: string]: string },
directedGraph: Graph
): string {
Expand All @@ -18,6 +18,14 @@ export function generateSqlQuery(
const startingNode = path[0][0].left;
let query = `${tableSchemaSqlMap[startingNode]}`;

/**
* If the starting node is not a join node, then return the query as is.
* It means that the query is a single node query.
*/
if (!isJoinNode(path[0][0])) {
return query;
}

const visitedNodes = new Map();

for (let i = 0; i < path.length; i++) {
Expand All @@ -28,6 +36,11 @@ export function generateSqlQuery(
}
for (let j = 0; j < path[i].length; j++) {
const currentEdge = path[i][j];

if (!isJoinNode(currentEdge)) {
continue;
}

const visitedFrom = visitedNodes.get(currentEdge.right);

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

console.log('directedGraph', directedGraph);

const baseSql = generateSqlQuery(
cubeQuery.joinPath || [],
cubeQuery.joinPaths || [],
tableSchemaSqlMap,
directedGraph
);
Expand Down
23 changes: 20 additions & 3 deletions meerkat-core/src/types/cube-types/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ interface QueryTimeDimension {
* Join Edge data type.
*/

interface JoinEdge {
interface JoinNode {
/**
* Left node.
*/
Expand Down Expand Up @@ -152,6 +152,23 @@ interface JoinEdge {
*/
}

/**
* Single node data type.
* This is the case when there is no join. Just a single node.
*/
interface SingleNode {
/**
* Left node.
*/
left: Member;
}

type JoinPath = [JoinNode | SingleNode, ...JoinNode[]];

export const isJoinNode = (node: JoinNode | SingleNode): node is JoinNode => {
return 'right' in node;
};

/**
* Incoming network query data type.
*/
Expand All @@ -163,7 +180,7 @@ interface Query {
dimensions?: (Member | TimeMember)[];
filters?: MeerkatQueryFilter[];
timeDimensions?: QueryTimeDimension[];
joinPath?: JoinEdge[][];
joinPaths?: JoinPath[];
segments?: Member[];
limit?: null | number;
offset?: number;
Expand Down Expand Up @@ -196,7 +213,7 @@ export {
ApiScopes,
ApiType,
FilterOperator,
JoinEdge,
JoinPath,
LogicalAndFilter,
LogicalOrFilter,
MeerkatQueryFilter,
Expand Down
77 changes: 77 additions & 0 deletions meerkat-core/src/utils/get-possible-nodes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ describe('Table schema functions', () => {
],
];

const singleNodeJoinPath = [
[
{
left: 'node1',
},
],
];

const intermediateJoinPath = [
[
{
Expand Down Expand Up @@ -222,6 +230,75 @@ describe('Table schema functions', () => {
],
];

it('Test single node join path', async () => {
const nestedSchema = await getNestedTableSchema(
tableSchema,
singleNodeJoinPath,
1
);

expect(nestedSchema).toEqual({
name: 'node1',
measures: [],
dimensions: [
{
schema: {
name: 'id',
sql: 'node1.id',
},
children: [
{
name: 'node2',
measures: [],
dimensions: [
{
schema: {
name: 'id',
sql: 'node2.id',
},
children: [],
},
{
schema: {
name: 'node11_id',
sql: 'node2.node11_id',
},
children: [],
},
],
},
{
name: 'node3',
measures: [],
dimensions: [
{
schema: {
name: 'id',
sql: 'node3.id',
},
children: [],
},
],
},
{
name: 'node6',
measures: [],
dimensions: [
{
schema: {
name: 'id',
sql: 'node6.id',
},
children: [],
},
],
},
],
},
],
});
});

it('Test basic join path with depth 0 (should return original graph)', async () => {
const nestedSchema = await getNestedTableSchema(
tableSchema,
Expand Down
26 changes: 23 additions & 3 deletions meerkat-core/src/utils/get-possible-nodes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Graph, checkLoopInGraph, createDirectedGraph } from '../joins/joins';
import { Dimension, JoinEdge, Measure, TableSchema } from '../types/cube-types';
import {
Dimension,
JoinPath,
Measure,
TableSchema,
isJoinNode,
} from '../types/cube-types';

export interface NestedMeasure {
schema: Measure;
Expand All @@ -19,7 +25,7 @@ export interface NestedTableSchema {

export const getNestedTableSchema = (
tableSchemas: TableSchema[],
joinPath: JoinEdge[][],
joinPath: JoinPath[],
depth: number
) => {
const tableSchemaSqlMap: { [key: string]: string } = {};
Expand Down Expand Up @@ -58,12 +64,22 @@ export const getNestedTableSchema = (
const checkedPaths: { [key: string]: boolean } = {};

const buildNestedSchema = (
edges: JoinEdge[],
edges: JoinPath,
index: number,
nestedTableSchema: NestedTableSchema,
tableSchemas: TableSchema[]
): NestedTableSchema => {
const edge = edges[index];

/**
* If there is no right table, return the nested schema immediately
* This means there is a single node in the path.
*/

if (!isJoinNode(edge)) {
return nestedTableSchema;
}

// If the path has been checked before, return the nested schema immediately
const pathKey = `${edge.left}-${edge.right}-${edge.on}`;
if (checkedPaths[pathKey]) {
Expand All @@ -82,6 +98,10 @@ export const getNestedTableSchema = (
(schema) => schema.name === edge.right
) as TableSchema;

if (!rightSchema) {
throw new Error(`The schema for ${edge.right} does not exist.`);
}

// Mark the path as checked
checkedPaths[pathKey] = true;

Expand Down
37 changes: 31 additions & 6 deletions meerkat-node/src/__tests__/joins.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ describe('Joins Tests', () => {
BOOK_SCHEMA.joins = [];
const query = {
measures: ['books.total_book_count', 'authors.total_author_count'],
joinPath: [
joinPaths: [
[
{
left: 'authors',
Expand Down Expand Up @@ -340,6 +340,31 @@ describe('Joins Tests', () => {
);
});

it('Single node in the path', async () => {
const query = {
measures: [],
filters: [],
dimensions: ['orders.order_id'],
joinPaths: [
[
{
left: 'orders',
on: 'order_id',
},
],
],
};
const sql = await cubeQueryToSQL(query, [AUTHOR_SCHEMA, ORDER_SCHEMA]);
console.info(`SQL for Simple Cube Query: `, sql);
const output = await duckdbExec(sql);
const parsedOutput = JSON.parse(JSON.stringify(output));
console.info('parsedOutput', parsedOutput);
expect(sql).toEqual(
'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'
);
expect(parsedOutput).toHaveLength(11);
});

it('Three tables join - Direct', async () => {
const DEMO_SCHEMA = structuredClone(ORDER_SCHEMA);

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

const query = {
measures: ['orders.total_order_amount'],
joinPath: [
joinPaths: [
[
{
left: 'customers',
Expand Down Expand Up @@ -408,7 +433,7 @@ describe('Joins Tests', () => {

const query = {
measures: ['orders.total_order_amount'],
joinPath: [
joinPaths: [
[
{
left: 'customers',
Expand Down Expand Up @@ -465,7 +490,7 @@ describe('Joins Tests', () => {
it('Joins with Different Paths', async () => {
const query1 = {
measures: ['orders.total_order_amount'],
joinPath: [
joinPaths: [
[
{
left: 'customers',
Expand Down Expand Up @@ -512,7 +537,7 @@ describe('Joins Tests', () => {

const query2 = {
measures: ['orders.total_order_amount'],
joinPath: [
joinPaths: [
[
{
left: 'customers',
Expand Down Expand Up @@ -555,7 +580,7 @@ describe('Joins Tests', () => {
it('Success Join with filters', async () => {
const query = {
measures: ['orders.total_order_amount'],
joinPath: [
joinPaths: [
[
{
left: 'customers',
Expand Down

0 comments on commit 366cb79

Please sign in to comment.