Skip to content

Commit f1b19ea

Browse files
feat: added support for dot in field name (#136)
* added support for composite fields * build failure fix * revretd uneeded changes * reverted uneeded changes * fixed test failure * lint error fix * lint error fix * reverted uneeded changes * added repo.yml * added repo.yml * added test cases * styled code * styled code
1 parent b880cd1 commit f1b19ea

34 files changed

+713
-104
lines changed

.devrev/repo.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
deployable: true

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.90",
3+
"version": "0.0.91",
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.90",
3+
"version": "0.0.91",
44
"dependencies": {
55
"@swc/helpers": "~0.5.0"
66
},
Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
import { Query } from '../types/cube-types/query';
2+
import { TableSchema } from '../types/cube-types/table';
3+
import {
4+
LimitModifier,
5+
OrderModifier,
6+
QueryNodeType,
7+
ResultModifierType,
8+
} from '../types/duckdb-serialization-types';
9+
import { isSelectNode } from '../types/utils';
10+
import { cubeToDuckdbAST } from './ast-builder';
11+
12+
describe('cubeToDuckdbAST', () => {
13+
const mockTableSchema: TableSchema = {
14+
name: 'test_table',
15+
sql: 'test_table',
16+
measures: [
17+
{
18+
name: 'measure1',
19+
sql: 'test_table.measure1',
20+
type: 'number',
21+
},
22+
],
23+
dimensions: [
24+
{
25+
name: 'dimension1',
26+
sql: 'test_table.dimension1',
27+
type: 'string',
28+
},
29+
],
30+
};
31+
32+
it('should return null if table schema is null', () => {
33+
const result = cubeToDuckdbAST({} as Query, null as unknown as TableSchema);
34+
expect(result).toBeNull();
35+
});
36+
37+
it('should handle basic query', () => {
38+
const query: Query = {
39+
measures: ['test_table.measure1'],
40+
dimensions: ['test_table.dimension1'],
41+
};
42+
43+
const result = cubeToDuckdbAST(query, mockTableSchema);
44+
expect(result).not.toBeNull();
45+
expect(result.node.type).toBe(QueryNodeType.SELECT_NODE);
46+
expect(result.node.group_expressions).toHaveLength(1);
47+
expect(result.node.group_expressions).toEqual([
48+
{
49+
alias: '',
50+
class: 'COLUMN_REF',
51+
column_names: ['test_table__dimension1'],
52+
type: 'COLUMN_REF',
53+
},
54+
]);
55+
});
56+
57+
it('should handle filters for dimensions (WHERE clause)', () => {
58+
const query: Query = {
59+
measures: ['test_table.measure1'],
60+
dimensions: ['test_table.dimension1'],
61+
filters: [
62+
{
63+
member: 'test_table.dimension1',
64+
operator: 'equals',
65+
values: ['value1'],
66+
},
67+
],
68+
};
69+
70+
const result = cubeToDuckdbAST(query, mockTableSchema);
71+
expect(result).not.toBeNull();
72+
expect(result.node.type).toBe(QueryNodeType.SELECT_NODE);
73+
expect(result.node.where_clause).toEqual({
74+
alias: '',
75+
class: 'COMPARISON',
76+
left: {
77+
alias: '',
78+
class: 'COLUMN_REF',
79+
column_names: ['test_table__dimension1'],
80+
type: 'COLUMN_REF',
81+
},
82+
right: {
83+
alias: '',
84+
class: 'CONSTANT',
85+
type: 'VALUE_CONSTANT',
86+
value: {
87+
is_null: false,
88+
type: {
89+
id: 'VARCHAR',
90+
type_info: null,
91+
},
92+
value: 'value1',
93+
},
94+
},
95+
type: 'COMPARE_EQUAL',
96+
});
97+
});
98+
99+
it('should handle filters for measures (HAVING clause)', () => {
100+
const query: Query = {
101+
measures: ['test_table.measure1'],
102+
dimensions: ['test_table.dimension1'],
103+
filters: [
104+
{
105+
member: 'test_table.measure1',
106+
operator: 'gt',
107+
values: ['100'],
108+
},
109+
],
110+
};
111+
112+
const result = cubeToDuckdbAST(query, mockTableSchema);
113+
expect(result.node.type).toBe(QueryNodeType.SELECT_NODE);
114+
expect(result.node.having).toEqual({
115+
alias: '',
116+
class: 'COMPARISON',
117+
left: {
118+
alias: '',
119+
class: 'COLUMN_REF',
120+
column_names: ['test_table__measure1'],
121+
type: 'COLUMN_REF',
122+
},
123+
right: {
124+
alias: '',
125+
class: 'CONSTANT',
126+
type: 'COMPARE_GREATERTHAN',
127+
type: 'VALUE_CONSTANT',
128+
value: {
129+
is_null: false,
130+
type: {
131+
id: 'DECIMAL',
132+
type_info: {
133+
alias: '',
134+
scale: 0,
135+
type: 'DECIMAL_TYPE_INFO',
136+
width: 3,
137+
},
138+
},
139+
value: 100,
140+
},
141+
},
142+
type: 'COMPARE_GREATERTHAN',
143+
});
144+
});
145+
146+
it('should handle order by clause', () => {
147+
const query: Query = {
148+
measures: ['test_table.measure1'],
149+
dimensions: ['test_table.dimension1'],
150+
order: {
151+
'test_table.dimension1': 'asc',
152+
},
153+
};
154+
155+
const result = cubeToDuckdbAST(query, mockTableSchema);
156+
expect(result).not.toBeNull();
157+
expect(result.node.type).toBe(QueryNodeType.SELECT_NODE);
158+
if (isSelectNode(result.node)) {
159+
const orderModifier = result.node.modifiers[0] as OrderModifier;
160+
expect(orderModifier.type).toBe(ResultModifierType.ORDER_MODIFIER);
161+
expect(orderModifier.orders[0].expression).toEqual({
162+
alias: '',
163+
class: 'COLUMN_REF',
164+
column_names: ['test_table__dimension1'],
165+
type: 'COLUMN_REF',
166+
});
167+
}
168+
});
169+
170+
it('should handle limit and offset', () => {
171+
const query: Query = {
172+
measures: ['test_table.measure1'],
173+
dimensions: ['test_table.dimension1'],
174+
limit: 10,
175+
offset: 5,
176+
};
177+
178+
const result = cubeToDuckdbAST(query, mockTableSchema);
179+
expect(result).not.toBeNull();
180+
expect(result.node.type).toBe(QueryNodeType.SELECT_NODE);
181+
if (isSelectNode(result.node)) {
182+
expect(result.node.modifiers).toHaveLength(1);
183+
const limitModifier = result.node.modifiers[0] as LimitModifier;
184+
expect(limitModifier).toEqual({
185+
limit: {
186+
alias: '',
187+
class: 'CONSTANT',
188+
type: 'VALUE_CONSTANT',
189+
value: {
190+
is_null: false,
191+
type: {
192+
id: 'INTEGER',
193+
type_info: null,
194+
},
195+
value: 10,
196+
},
197+
},
198+
offset: {
199+
alias: '',
200+
class: 'CONSTANT',
201+
type: 'VALUE_CONSTANT',
202+
value: {
203+
is_null: false,
204+
type: {
205+
id: 'INTEGER',
206+
type_info: null,
207+
},
208+
value: 5,
209+
},
210+
},
211+
type: 'LIMIT_MODIFIER',
212+
});
213+
}
214+
});
215+
216+
it('should handle complex query', () => {
217+
const query: Query = {
218+
measures: ['test_table.measure1'],
219+
dimensions: ['test_table.dimension1'],
220+
filters: [
221+
{
222+
member: 'test_table.dimension1',
223+
operator: 'equals',
224+
values: ['value1'],
225+
},
226+
{
227+
member: 'test_table.measure1',
228+
operator: 'gt',
229+
values: ['100'],
230+
},
231+
],
232+
order: {
233+
'test_table.dimension1': 'asc',
234+
'test_table.measure1': 'desc',
235+
},
236+
limit: 10,
237+
offset: 5,
238+
};
239+
240+
const result = cubeToDuckdbAST(query, mockTableSchema);
241+
expect(result).not.toBeNull();
242+
expect(result.node.type).toBe(QueryNodeType.SELECT_NODE);
243+
expect(result.node.group_expressions).toHaveLength(1);
244+
expect(result.node.group_expressions[0]).toEqual({
245+
alias: '',
246+
class: 'COLUMN_REF',
247+
column_names: ['test_table__dimension1'],
248+
type: 'COLUMN_REF',
249+
});
250+
expect(result.node.where_clause).toEqual({
251+
alias: '',
252+
class: 'COMPARISON',
253+
left: {
254+
alias: '',
255+
class: 'COLUMN_REF',
256+
column_names: ['test_table__dimension1'],
257+
type: 'COLUMN_REF',
258+
},
259+
right: {
260+
alias: '',
261+
class: 'CONSTANT',
262+
type: 'VALUE_CONSTANT',
263+
value: {
264+
is_null: false,
265+
type: {
266+
id: 'VARCHAR',
267+
type_info: null,
268+
},
269+
value: 'value1',
270+
},
271+
},
272+
type: 'COMPARE_EQUAL',
273+
});
274+
expect(result.node.having).toEqual({
275+
alias: '',
276+
class: 'COMPARISON',
277+
left: {
278+
alias: '',
279+
class: 'COLUMN_REF',
280+
column_names: ['test_table__measure1'],
281+
type: 'COLUMN_REF',
282+
},
283+
right: {
284+
alias: '',
285+
class: 'CONSTANT',
286+
type: 'VALUE_CONSTANT',
287+
value: {
288+
is_null: false,
289+
type: {
290+
id: 'DECIMAL',
291+
type_info: {
292+
alias: '',
293+
scale: 0,
294+
type: 'DECIMAL_TYPE_INFO',
295+
width: 3,
296+
},
297+
},
298+
value: 100,
299+
},
300+
},
301+
type: 'COMPARE_GREATERTHAN',
302+
});
303+
expect(result.node.modifiers).toHaveLength(2);
304+
const orderModifier = result.node.modifiers[0] as OrderModifier;
305+
expect(orderModifier.type).toBe(ResultModifierType.ORDER_MODIFIER);
306+
expect(orderModifier.orders).toHaveLength(2);
307+
expect(orderModifier.orders[0].expression).toEqual({
308+
alias: '',
309+
class: 'COLUMN_REF',
310+
column_names: ['test_table__dimension1'],
311+
type: 'COLUMN_REF',
312+
});
313+
expect(orderModifier.orders[1].expression).toEqual({
314+
alias: '',
315+
class: 'COLUMN_REF',
316+
column_names: ['test_table__measure1'],
317+
type: 'COLUMN_REF',
318+
});
319+
const limitModifier = result.node.modifiers[1] as LimitModifier;
320+
expect(limitModifier).toEqual({
321+
limit: {
322+
alias: '',
323+
class: 'CONSTANT',
324+
type: 'VALUE_CONSTANT',
325+
value: {
326+
is_null: false,
327+
type: { id: 'INTEGER', type_info: null },
328+
value: 10,
329+
},
330+
},
331+
offset: {
332+
alias: '',
333+
class: 'CONSTANT',
334+
type: 'VALUE_CONSTANT',
335+
value: {
336+
is_null: false,
337+
type: { id: 'INTEGER', type_info: null },
338+
value: 5,
339+
},
340+
},
341+
type: 'LIMIT_MODIFIER',
342+
});
343+
});
344+
345+
it('should handle dimensions with multiple dots in their names', () => {
346+
const complexTableSchema: TableSchema = {
347+
name: 'test_table',
348+
sql: 'test_table',
349+
measures: [
350+
{
351+
name: 'measure.with.dots',
352+
sql: 'test_table.measure.with.dots',
353+
type: 'number',
354+
},
355+
],
356+
dimensions: [
357+
{
358+
name: 'dimension.with.dots',
359+
sql: 'test_table.dimension.with.dots',
360+
type: 'string',
361+
},
362+
],
363+
};
364+
365+
const query: Query = {
366+
measures: ['test_table.measure.with.dots'],
367+
dimensions: ['test_table.dimension.with.dots'],
368+
};
369+
370+
const result = cubeToDuckdbAST(query, complexTableSchema);
371+
expect(result.node.type).toBe(QueryNodeType.SELECT_NODE);
372+
expect(result.node.group_expressions).toHaveLength(1);
373+
expect(result.node.group_expressions[0]).toEqual({
374+
alias: '',
375+
class: 'COLUMN_REF',
376+
column_names: ['test_table__dimension__with__dots'],
377+
type: 'COLUMN_REF',
378+
});
379+
});
380+
});

0 commit comments

Comments
 (0)