Skip to content

Commit 874860d

Browse files
authored
Add context params to meerkat (#36)
1 parent 3f027b8 commit 874860d

File tree

10 files changed

+298
-7
lines changed

10 files changed

+298
-7
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.58",
3+
"version": "0.0.59",
44
"dependencies": {
55
"@swc/helpers": "~0.5.0",
66
"@devrev/meerkat-core": "*",

meerkat-browser/src/browser-cube-to-sql/browser-cube-to-sql.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import {
22
BASE_TABLE_NAME,
3+
ContextParams,
34
Query,
45
TableSchema,
56
applyFilterParamsToBaseSQL,
67
applyProjectionToSQLQuery,
78
astDeserializerQuery,
89
cubeToDuckdbAST,
910
deserializeQuery,
11+
detectApplyContextParamsToBaseSQL,
1012
getFilterParamsAST,
1113
} from '@devrev/meerkat-core';
1214
import { AsyncDuckDBConnection } from '@duckdb/duckdb-wasm';
1315
export const cubeQueryToSQL = async (
1416
connection: AsyncDuckDBConnection,
1517
cubeQuery: Query,
16-
tableSchema: TableSchema
18+
tableSchema: TableSchema,
19+
contextParams?: ContextParams
1720
) => {
1821
const ast = cubeToDuckdbAST(cubeQuery, tableSchema);
1922
if (!ast) {
@@ -48,11 +51,19 @@ export const cubeQueryToSQL = async (
4851
});
4952
}
5053

51-
const baseQuery = applyFilterParamsToBaseSQL(
54+
const filterParamQuery = applyFilterParamsToBaseSQL(
5255
tableSchema.sql,
5356
filterParamsSQL
5457
);
5558

59+
/**
60+
* Replace CONTEXT_PARAMS with context params
61+
*/
62+
const baseQuery = detectApplyContextParamsToBaseSQL(
63+
filterParamQuery,
64+
contextParams || {}
65+
);
66+
5667
/**
5768
* Replace BASE_TABLE_NAME with cube query
5869
*/

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.58",
3+
"version": "0.0.59",
44
"dependencies": {
55
"@swc/helpers": "~0.5.0"
66
},
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import {
2+
applyContextParamsToBaseSQL,
3+
detectAllContextParams,
4+
detectApplyContextParamsToBaseSQL,
5+
} from './context-params-ast';
6+
7+
describe('detectAllContextParams function', () => {
8+
it('should extract all CONTEXT_PARAMS from SQL string', () => {
9+
const sql = 'SELECT * FROM orders WHERE ${CONTEXT_PARAMS.current_user.id}';
10+
const expected = [
11+
{
12+
memberKey: 'current_user.id',
13+
matchKey: '${CONTEXT_PARAMS.current_user.id}',
14+
},
15+
];
16+
expect(detectAllContextParams(sql)).toEqual(expected);
17+
});
18+
19+
it('Should match with multiple keys in the same string', () => {
20+
const sql =
21+
'SELECT * FROM orders WHERE ${CONTEXT_PARAMS.current_user.id} AND ${CONTEXT_PARAMS.current_user.name}';
22+
const expected = [
23+
{
24+
memberKey: 'current_user.id',
25+
matchKey: '${CONTEXT_PARAMS.current_user.id}',
26+
},
27+
{
28+
memberKey: 'current_user.name',
29+
matchKey: '${CONTEXT_PARAMS.current_user.name}',
30+
},
31+
];
32+
expect(detectAllContextParams(sql)).toEqual(expected);
33+
});
34+
35+
it('should work with CONTEXT_PARAMS with deeper object keys', () => {
36+
const sql =
37+
'SELECT * FROM orders WHERE ${CONTEXT_PARAMS.current_user.id} AND ${CONTEXT_PARAMS.current_user.name} AND ${CONTEXT_PARAMS.current_user.address.city}';
38+
const expected = [
39+
{
40+
memberKey: 'current_user.id',
41+
matchKey: '${CONTEXT_PARAMS.current_user.id}',
42+
},
43+
{
44+
memberKey: 'current_user.name',
45+
matchKey: '${CONTEXT_PARAMS.current_user.name}',
46+
},
47+
{
48+
memberKey: 'current_user.address.city',
49+
matchKey: '${CONTEXT_PARAMS.current_user.address.city}',
50+
},
51+
];
52+
expect(detectAllContextParams(sql)).toEqual(expected);
53+
});
54+
});
55+
56+
describe('applyContextParamsToBaseSQL function', () => {
57+
it('should replace all CONTEXT_PARAMS with their values', () => {
58+
const baseSQL =
59+
'SELECT * FROM orders WHERE ${CONTEXT_PARAMS.current_user.id}';
60+
const contextParamsSQL = [
61+
{
62+
memberKey: 'current_user.id',
63+
matchKey: '${CONTEXT_PARAMS.current_user.id}',
64+
contextParamSQL: "'123'",
65+
},
66+
];
67+
const expected = "SELECT * FROM orders WHERE '123'";
68+
expect(applyContextParamsToBaseSQL(baseSQL, contextParamsSQL)).toEqual(
69+
expected
70+
);
71+
});
72+
it('should replace all CONTEXT_PARAMS with their values when there are multiple CONTEXT_PARAMS', () => {
73+
const baseSQL =
74+
'SELECT * FROM orders WHERE ${CONTEXT_PARAMS.current_user.id} AND ${CONTEXT_PARAMS.current_user.name}';
75+
const contextParamsSQL = [
76+
{
77+
memberKey: 'current_user.id',
78+
matchKey: '${CONTEXT_PARAMS.current_user.id}',
79+
contextParamSQL: "'123'",
80+
},
81+
{
82+
memberKey: 'current_user.name',
83+
matchKey: '${CONTEXT_PARAMS.current_user.name}',
84+
contextParamSQL: "'John Doe'",
85+
},
86+
];
87+
const expected = "SELECT * FROM orders WHERE '123' AND 'John Doe'";
88+
expect(applyContextParamsToBaseSQL(baseSQL, contextParamsSQL)).toEqual(
89+
expected
90+
);
91+
});
92+
it('should replace all CONTEXT_PARAMS with their values when there are multiple CONTEXT_PARAMS with deeper object keys', () => {
93+
const baseSQL =
94+
'SELECT * FROM orders WHERE ${CONTEXT_PARAMS.current_user.id} AND ${CONTEXT_PARAMS.current_user.name} AND ${CONTEXT_PARAMS.current_user.address.city}';
95+
const contextParamsSQL = [
96+
{
97+
memberKey: 'current_user.id',
98+
matchKey: '${CONTEXT_PARAMS.current_user.id}',
99+
contextParamSQL: "'123'",
100+
},
101+
{
102+
memberKey: 'current_user.name',
103+
matchKey: '${CONTEXT_PARAMS.current_user.name}',
104+
contextParamSQL: "'John Doe'",
105+
},
106+
{
107+
memberKey: 'current_user.address.city',
108+
matchKey: '${CONTEXT_PARAMS.current_user.address.city}',
109+
contextParamSQL: "'New York'",
110+
},
111+
];
112+
const expected =
113+
"SELECT * FROM orders WHERE '123' AND 'John Doe' AND 'New York'";
114+
expect(applyContextParamsToBaseSQL(baseSQL, contextParamsSQL)).toEqual(
115+
expected
116+
);
117+
});
118+
});
119+
120+
describe('detectApplyContextParamsToBaseSQL function', () => {
121+
it('should return an array of contextParamsSQL', () => {
122+
const baseSQL =
123+
'SELECT * FROM orders WHERE ${CONTEXT_PARAMS.current_user.id} AND ${CONTEXT_PARAMS.current_user.name} AND ${CONTEXT_PARAMS.current_user.address.city}';
124+
const contextParams = {
125+
'current_user.id': "'123'",
126+
'current_user.name': "'John Doe'",
127+
'current_user.address.city': "'New York'",
128+
};
129+
130+
const expected = `SELECT * FROM orders WHERE '123' AND 'John Doe' AND 'New York'`;
131+
132+
expect(detectApplyContextParamsToBaseSQL(baseSQL, contextParams)).toEqual(
133+
expected
134+
);
135+
});
136+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { ContextParams } from '../types/cube-types/table';
2+
3+
export const detectAllContextParams = (
4+
sql: string
5+
): {
6+
memberKey: string;
7+
matchKey: string;
8+
}[] => {
9+
const regex = /\$\{CONTEXT_PARAMS\.([^}]*)\}/g;
10+
const matches = [];
11+
let match;
12+
13+
while ((match = regex.exec(sql)) !== null) {
14+
matches.push({
15+
memberKey: match[1],
16+
matchKey: match[0],
17+
});
18+
}
19+
20+
return matches;
21+
};
22+
23+
export const applyContextParamsToBaseSQL = (
24+
baseSQL: string,
25+
contextParamsSQL: {
26+
memberKey: string;
27+
contextParamSQL: string;
28+
matchKey: string;
29+
}[]
30+
) => {
31+
let newSQL = baseSQL;
32+
for (const contextParamSQL of contextParamsSQL) {
33+
// Replace matchKey with contextParamSQL
34+
newSQL = newSQL.replace(
35+
contextParamSQL.matchKey,
36+
contextParamSQL.contextParamSQL
37+
);
38+
}
39+
return newSQL;
40+
};
41+
42+
export const detectApplyContextParamsToBaseSQL = (
43+
baseSQL: string,
44+
contextParams: ContextParams
45+
) => {
46+
const contextParamsSQL = [];
47+
const contextParamsKeys = detectAllContextParams(baseSQL);
48+
49+
for (const contextParamsKey of contextParamsKeys) {
50+
const contextParamSQL = contextParams[contextParamsKey.memberKey];
51+
if (contextParamSQL) {
52+
contextParamsSQL.push({
53+
memberKey: contextParamsKey.memberKey,
54+
matchKey: contextParamsKey.matchKey,
55+
contextParamSQL: contextParamSQL,
56+
});
57+
}
58+
}
59+
60+
return applyContextParamsToBaseSQL(baseSQL, contextParamsSQL);
61+
};

meerkat-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './ast-builder/ast-builder';
22
export * from './ast-deserializer/ast-deserializer';
3+
export { detectApplyContextParamsToBaseSQL } from './context-params/context-params-ast';
34
export * from './cube-measure-transformer/cube-measure-transformer';
45
export * from './cube-to-duckdb/cube-filter-to-duckdb';
56
export {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,7 @@ export type TableSchema = {
3232
measures: Measure[];
3333
dimensions: Dimension[];
3434
};
35+
36+
export interface ContextParams {
37+
[key: string]: string;
38+
}

meerkat-node/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-node",
3-
"version": "0.0.58",
3+
"version": "0.0.59",
44
"dependencies": {
55
"@swc/helpers": "~0.5.0",
66
"@devrev/meerkat-core": "*",
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { cubeQueryToSQL } from '../cube-to-sql/cube-to-sql';
2+
import { duckdbExec } from '../duckdb-exec';
3+
const SCHEMA = {
4+
name: 'orders',
5+
sql: 'select * from ${CONTEXT_PARAMS.TABLE_NAME}',
6+
measures: [],
7+
dimensions: [
8+
{
9+
name: 'id',
10+
sql: 'id',
11+
type: 'number',
12+
},
13+
{
14+
name: 'date',
15+
sql: 'date',
16+
type: 'string',
17+
},
18+
{
19+
name: 'status',
20+
sql: 'status',
21+
type: 'string',
22+
},
23+
{
24+
name: 'amount',
25+
sql: 'amount',
26+
type: 'number',
27+
},
28+
],
29+
};
30+
describe('context-param-tests', () => {
31+
beforeAll(async () => {
32+
await duckdbExec(`CREATE TABLE orders(
33+
id INTEGER PRIMARY KEY,
34+
date DATE,
35+
status VARCHAR,
36+
amount DECIMAL
37+
);`);
38+
await duckdbExec(`INSERT INTO orders(id, date, status, amount)
39+
VALUES (1, DATE '2022-01-21', 'completed', 99.99),
40+
(2, DATE '2022-02-10', 'completed', 200.50),
41+
(3, DATE '2022-01-25', 'completed', 150.00),
42+
(4, DATE '2022-03-01', 'cancelled', 40.00),
43+
(5, DATE '2022-01-28', 'completed', 80.75),
44+
(6, DATE '2022-02-15', 'pending', 120.00),
45+
(7, DATE '2022-04-01', 'completed', 210.00);`);
46+
});
47+
48+
it('Should apply context params to base SQL', async () => {
49+
const query = {
50+
measures: ['*'],
51+
filters: [
52+
{
53+
member: 'orders.status',
54+
operator: 'equals',
55+
values: ['pending'],
56+
},
57+
],
58+
dimensions: [],
59+
};
60+
const sql = await cubeQueryToSQL(query, SCHEMA, {
61+
TABLE_NAME: 'orders',
62+
});
63+
console.info('SQL: ', sql);
64+
const output: any = await duckdbExec(sql);
65+
expect(output).toHaveLength(1);
66+
});
67+
});

meerkat-node/src/cube-to-sql/cube-to-sql.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import {
22
BASE_TABLE_NAME,
3+
ContextParams,
34
Query,
45
TableSchema,
56
applyFilterParamsToBaseSQL,
67
applyProjectionToSQLQuery,
78
astDeserializerQuery,
89
cubeToDuckdbAST,
910
deserializeQuery,
11+
detectApplyContextParamsToBaseSQL,
1012
getFilterParamsAST,
1113
} from '@devrev/meerkat-core';
1214
import { duckdbExec } from '../duckdb-exec';
1315

1416
export const cubeQueryToSQL = async (
1517
cubeQuery: Query,
16-
tableSchema: TableSchema
18+
tableSchema: TableSchema,
19+
contextParams?: ContextParams
1720
) => {
1821
const ast = cubeToDuckdbAST(cubeQuery, tableSchema);
1922

@@ -54,11 +57,19 @@ export const cubeQueryToSQL = async (
5457
});
5558
}
5659

57-
const baseQuery = applyFilterParamsToBaseSQL(
60+
const filterParamQuery = applyFilterParamsToBaseSQL(
5861
tableSchema.sql,
5962
filterParamsSQL
6063
);
6164

65+
/**
66+
* Replace CONTEXT_PARAMS with context params
67+
*/
68+
const baseQuery = detectApplyContextParamsToBaseSQL(
69+
filterParamQuery,
70+
contextParams || {}
71+
);
72+
6273
/**
6374
* Replace BASE_TABLE_NAME with cube query
6475
*/

0 commit comments

Comments
 (0)