Skip to content

Commit c89ed82

Browse files
authored
fixes for alias handling (#156)
* fixes for alias handling * merge main
1 parent 9982207 commit c89ed82

20 files changed

+355
-251
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.100",
3+
"version": "0.0.101",
44
"dependencies": {
55
"tslib": "^2.3.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.100",
3+
"version": "0.0.101",
44
"dependencies": {
55
"tslib": "^2.3.0"
66
},

meerkat-core/src/cube-measure-transformer/cube-measure-transformer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@ export const cubeMeasureToSQLSelectString = (
3434
if (i > 0) {
3535
base += ', ';
3636
}
37+
38+
// Note that tableSchemaName is not necessarily the same as tableSchema.name
39+
// since tableSchema might be the merged tableSchema.
3740
let meerkatReplacedSqlString = meerkatPlaceholderReplacer(
3841
measureSchema.sql,
39-
tableSchema.name
42+
tableSchemaName,
43+
tableSchema
4044
);
4145

4246
/**

meerkat-core/src/get-final-base-sql/get-final-base-sql.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('get final base sql', () => {
4444
getQueryOutput,
4545
});
4646
expect(result).toEqual(
47-
'SELECT *, amount AS orders__amount, status AS orders__status FROM (select * from orders WHERE ((orders.status IS NOT NULL))) AS orders'
47+
'SELECT amount AS orders__amount, status AS orders__status, * FROM (select * from orders WHERE ((orders.status IS NOT NULL))) AS orders'
4848
);
4949
});
5050
it('should not return measures in the projected base sql when filter param not passed', async () => {
@@ -62,7 +62,7 @@ describe('get final base sql', () => {
6262
getQueryOutput,
6363
});
6464
expect(result).toEqual(
65-
'SELECT *, amount AS orders__amount, status AS orders__status FROM (select * from orders WHERE TRUE) AS orders'
65+
'SELECT amount AS orders__amount, status AS orders__status, * FROM (select * from orders WHERE TRUE) AS orders'
6666
);
6767
});
6868

@@ -110,7 +110,7 @@ describe('get final base sql', () => {
110110
getQueryOutput,
111111
});
112112
expect(result).toEqual(
113-
'SELECT *, amount AS "order amount", status AS "order status" FROM (select * from orders WHERE ((orders.status IS NOT NULL))) AS orders'
113+
'SELECT amount AS "order amount", status AS "order status", * FROM (select * from orders WHERE ((orders.status IS NOT NULL))) AS orders'
114114
);
115115
});
116116
});

meerkat-core/src/get-wrapped-base-query-with-projections/get-aliased-columns-from-filters.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,40 +116,42 @@ const getFilterProjections = ({
116116
};
117117

118118
export const getAliasedColumnsFromFilters = ({
119-
baseSql,
120119
meerkatFilters,
121120
tableSchema,
122121
aliasedColumnSet,
123122
query,
124123
}: {
125124
meerkatFilters?: MeerkatQueryFilter[];
126125
tableSchema: TableSchema;
127-
baseSql: string;
128126
aliasedColumnSet: Set<string>;
129127
query: Query;
130128
}) => {
131-
let sql = baseSql;
129+
const parts: string[] = [];
132130
const { measures } = query;
133131
meerkatFilters?.forEach((filter) => {
134132
if ('and' in filter) {
135133
// Traverse through the passed 'and' filters
136-
sql += getAliasedColumnsFromFilters({
137-
baseSql: '',
134+
const sql = getAliasedColumnsFromFilters({
138135
meerkatFilters: filter.and,
139136
tableSchema,
140137
aliasedColumnSet,
141138
query,
142139
});
140+
if (sql) {
141+
parts.push(sql);
142+
}
143143
}
144144
if ('or' in filter) {
145145
// Traverse through the passed 'or' filters
146-
sql += getAliasedColumnsFromFilters({
147-
baseSql: '',
146+
const sql = getAliasedColumnsFromFilters({
148147
tableSchema,
149148
meerkatFilters: filter.or,
150149
aliasedColumnSet,
151150
query,
152151
});
152+
if (sql) {
153+
parts.push(sql);
154+
}
153155
}
154156
if ('member' in filter) {
155157
const {
@@ -170,8 +172,11 @@ export const getAliasedColumnsFromFilters = ({
170172
aliasedColumnSet.add(aliasKey);
171173
}
172174
// Add the alias key to the set. So we have a reference to all the previously selected members.
173-
sql += `, ${memberSql}`;
175+
const sql = memberSql;
176+
if (sql) {
177+
parts.push(sql);
178+
}
174179
}
175180
});
176-
return sql;
181+
return parts.join(', ');
177182
};

meerkat-core/src/get-wrapped-base-query-with-projections/get-wrapped-base-query-with-projections.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { getSelectReplacedSql } from '../cube-measure-transformer/cube-measure-transformer';
22
import { Query, TableSchema } from '../types/cube-types';
33
import { getAliasedColumnsFromFilters } from './get-aliased-columns-from-filters';
4-
import {
5-
getProjectionClause
6-
} from './get-projection-clause';
4+
import { getProjectionClause } from './get-projection-clause';
75

86
interface GetWrappedBaseQueryWithProjectionsParams {
97
baseQuery: string;
@@ -32,19 +30,17 @@ export const getWrappedBaseQueryWithProjections = ({
3230

3331
const aliasFromFilters = getAliasedColumnsFromFilters({
3432
aliasedColumnSet,
35-
baseSql: 'SELECT *',
3633
// setting measures to empty array, since we don't want to project measures present in the filters in the base query
3734
tableSchema: tableSchema,
3835
query,
3936
meerkatFilters: query.filters,
4037
});
4138

42-
const formattedMemberProjection = memberProjections
43-
? `, ${memberProjections}`
44-
: '';
45-
46-
const finalAliasedColumnsClause =
47-
aliasFromFilters + formattedMemberProjection;
39+
const parts = [aliasFromFilters, memberProjections].filter(
40+
(part) => part !== ''
41+
);
42+
parts.push('*');
43+
const finalAliasedColumnsClause = 'SELECT ' + parts.join(', ');
4844

4945
const sqlWithFilterProjects = getSelectReplacedSql(
5046
newBaseSql,
Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,70 @@
1+
import { TableSchema } from '../types/cube-types';
12
import { meerkatPlaceholderReplacer } from './meerkat-placeholder-replacer';
23

3-
describe("meerkatPlaceholderReplacer", () => {
4-
it("should not replace placeholders with tableName if placeholder pattern doesnt end in .", () => {
5-
const sql = "SELECT * FROM {MEERKAT}fieldName";
6-
const tableName = "customers";
7-
expect(meerkatPlaceholderReplacer(sql, tableName)).toEqual("SELECT * FROM {MEERKAT}fieldName");
8-
});
9-
10-
it("should replace multiple placeholders in the SQL query", () => {
11-
const sql = "SELECT {MEERKAT}.a, {MEERKAT}.b FROM orders";
12-
const tableName = "orders";
13-
expect(meerkatPlaceholderReplacer(sql, tableName)).toEqual('SELECT orders__a, orders__b FROM orders');
14-
});
15-
16-
it("should be case sensitive", () => {
17-
const sql = "SELECT {meerkat}.a FROM orders";
18-
const tableName = "orders";
19-
expect(meerkatPlaceholderReplacer(sql, tableName)).toEqual('SELECT {meerkat}.a FROM orders');
20-
});
21-
22-
it("should replace the correct match", () => {
23-
const sql = "SELECT {MEERKAT.{MEERKAT}.}.a FROM customers";
24-
const tableName = "customers";
25-
expect(meerkatPlaceholderReplacer(sql, tableName)).toEqual("SELECT {MEERKAT.customers__}.a FROM customers");
26-
});
27-
28-
it("should handle empty SQL queries", () => {
29-
const sql = "";
30-
const tableName = "customers";
31-
expect(meerkatPlaceholderReplacer(sql, tableName)).toEqual('');
32-
});
33-
34-
it("should handle SQL queries without placeholders", () => {
35-
const sql = "SELECT * FROM customers.";
36-
const tableName = "orders";
37-
expect(meerkatPlaceholderReplacer(sql, tableName)).toEqual('SELECT * FROM customers.');
38-
});
39-
40-
4+
describe('meerkatPlaceholderReplacer', () => {
5+
let tableSchema: TableSchema;
6+
beforeEach(() => {
7+
tableSchema = {
8+
name: 'test',
9+
sql: 'test',
10+
measures: [
11+
{ name: 'measure1', sql: 'COUNT(*)', type: 'number', alias: 'alias1' },
12+
],
13+
dimensions: [],
14+
};
15+
});
16+
17+
it('should not replace placeholders with tableName if placeholder pattern doesnt end in .', () => {
18+
const sql = 'SELECT * FROM {MEERKAT}fieldName';
19+
const tableName = 'customers';
20+
expect(meerkatPlaceholderReplacer(sql, tableName, tableSchema)).toEqual(
21+
'SELECT * FROM {MEERKAT}fieldName'
22+
);
23+
});
24+
25+
it('should replace multiple placeholders in the SQL query', () => {
26+
const sql = 'SELECT {MEERKAT}.a, {MEERKAT}.b FROM orders';
27+
const tableName = 'orders';
28+
expect(meerkatPlaceholderReplacer(sql, tableName, tableSchema)).toEqual(
29+
'SELECT orders__a, orders__b FROM orders'
30+
);
31+
});
32+
33+
it('should be case sensitive', () => {
34+
const sql = 'SELECT {meerkat}.a FROM orders';
35+
const tableName = 'orders';
36+
expect(meerkatPlaceholderReplacer(sql, tableName, tableSchema)).toEqual(
37+
'SELECT {meerkat}.a FROM orders'
38+
);
39+
});
40+
41+
it('should replace the correct match', () => {
42+
const sql = 'SELECT {MEERKAT.{MEERKAT}.a}.a FROM customers';
43+
const tableName = 'customers';
44+
expect(meerkatPlaceholderReplacer(sql, tableName, tableSchema)).toEqual(
45+
'SELECT {MEERKAT.customers__a}.a FROM customers'
46+
);
47+
});
48+
49+
it('should handle empty SQL queries', () => {
50+
const sql = '';
51+
const tableName = 'customers';
52+
expect(meerkatPlaceholderReplacer(sql, tableName, tableSchema)).toEqual('');
53+
});
54+
55+
it('should handle SQL queries without placeholders', () => {
56+
const sql = 'SELECT * FROM customers.';
57+
const tableName = 'orders';
58+
expect(meerkatPlaceholderReplacer(sql, tableName, tableSchema)).toEqual(
59+
'SELECT * FROM customers.'
60+
);
61+
});
62+
63+
it('should replace placeholders with alias if provided', () => {
64+
const sql = 'SELECT {MEERKAT}.measure1 FROM orders';
65+
const tableName = 'orders';
66+
expect(meerkatPlaceholderReplacer(sql, tableName, tableSchema)).toEqual(
67+
'SELECT "alias1" FROM orders'
68+
);
69+
});
4170
});
Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1-
import { MEERKAT_OUTPUT_DELIMITER } from '../member-formatters/constants';
1+
import { getAliasFromSchema, getNamespacedKey } from '../member-formatters';
2+
import { TableSchema } from '../types/cube-types';
23

3-
export const meerkatPlaceholderReplacer = (sql: string, tableName: string) => {
4-
const tableNameEncapsulationRegEx = /\{MEERKAT\}\./g;
5-
return sql.replace(
6-
tableNameEncapsulationRegEx,
7-
tableName + MEERKAT_OUTPUT_DELIMITER
8-
);
4+
export const meerkatPlaceholderReplacer = (
5+
sql: string,
6+
originalTableName: string,
7+
tableSchema: TableSchema
8+
) => {
9+
const tableNameEncapsulationRegEx = /\{MEERKAT\}\.([a-zA-Z_][a-zA-Z0-9_]*)/g;
10+
return sql.replace(tableNameEncapsulationRegEx, (_, columnName) => {
11+
return getAliasFromSchema({
12+
name: getNamespacedKey(originalTableName, columnName),
13+
tableSchema,
14+
aliasContext: {
15+
isAstIdentifier: false,
16+
},
17+
});
18+
});
919
};

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

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

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ describe('cube-to-sql', () => {
9292
const sql = await cubeQueryToSQL({ query, tableSchemas: [TABLE_SCHEMA] });
9393
console.info(`SQL for Simple Cube Query: `, sql);
9494
expect(sql).toBe(
95-
`SELECT SUM(order_amount) AS "Total Order Amount" , "Customer ID" FROM (SELECT *, customer_id AS "Customer ID" FROM (select * from orders) AS orders) AS orders GROUP BY "Customer ID"`
95+
`SELECT SUM(order_amount) AS "Total Order Amount" , "Customer ID" FROM (SELECT customer_id AS "Customer ID", * FROM (select * from orders) AS orders) AS orders GROUP BY "Customer ID"`
9696
);
9797
const output = await duckdbExec(sql);
9898
expect(output.length).toEqual(7);
@@ -118,7 +118,7 @@ describe('cube-to-sql', () => {
118118
const sql = await cubeQueryToSQL({ query, tableSchemas: [TABLE_SCHEMA] });
119119
console.info(`SQL for Simple Cube Query: `, sql);
120120
expect(sql).toBe(
121-
`SELECT SUM(order_amount) AS "Total Order Amount" , "Customer ID" FROM (SELECT *, customer_id AS "Customer ID" FROM (select * from orders) AS orders) AS orders WHERE ("Customer ID" = '2') GROUP BY "Customer ID" HAVING ("Total Order Amount" = 100)`
121+
`SELECT SUM(order_amount) AS "Total Order Amount" , "Customer ID" FROM (SELECT customer_id AS "Customer ID", * FROM (select * from orders) AS orders) AS orders WHERE ("Customer ID" = '2') GROUP BY "Customer ID" HAVING ("Total Order Amount" = 100)`
122122
);
123123
const output = await duckdbExec(sql);
124124
expect(output).toEqual([
@@ -147,7 +147,7 @@ describe('cube-to-sql', () => {
147147
const sql = await cubeQueryToSQL({ query, tableSchemas: [TABLE_SCHEMA] });
148148
console.info(`SQL for Simple Cube Query: `, sql);
149149
expect(sql).toBe(
150-
`SELECT SUM(order_amount) AS "Total Order Amount" , "Customer ID" FROM (SELECT *, customer_id AS "Customer ID" FROM (select * from orders) AS orders) AS orders WHERE ("Customer ID" = '2') GROUP BY "Customer ID" ORDER BY "Total Order Amount" DESC`
150+
`SELECT SUM(order_amount) AS "Total Order Amount" , "Customer ID" FROM (SELECT customer_id AS "Customer ID", * FROM (select * from orders) AS orders) AS orders WHERE ("Customer ID" = '2') GROUP BY "Customer ID" ORDER BY "Total Order Amount" DESC`
151151
);
152152
const output = await duckdbExec(sql);
153153
expect(output).toEqual([
@@ -157,4 +157,45 @@ describe('cube-to-sql', () => {
157157
},
158158
]);
159159
});
160+
161+
it('Should handle alias that conflicts with another column', async () => {
162+
const tableSchemaWithConflict = {
163+
...TABLE_SCHEMA,
164+
dimensions: [
165+
{
166+
name: 'customer_id',
167+
sql: 'customer_id',
168+
type: 'string',
169+
alias: 'order_id', // This conflicts with the orderr_id dimension
170+
},
171+
],
172+
};
173+
174+
const query: Query = {
175+
measures: ['orders.total_order_amount'],
176+
filters: [
177+
{
178+
member: 'orders.customer_id',
179+
operator: 'equals',
180+
values: ['2'],
181+
},
182+
],
183+
dimensions: ['orders.customer_id'],
184+
};
185+
const sql = await cubeQueryToSQL({
186+
query,
187+
tableSchemas: [tableSchemaWithConflict],
188+
});
189+
console.info(`SQL for Simple Cube Query: `, sql);
190+
expect(sql).toBe(
191+
`SELECT SUM(order_amount) AS "Total Order Amount" , "order_id" FROM (SELECT customer_id AS "order_id", * FROM (select * from orders) AS orders) AS orders WHERE (order_id = '2') GROUP BY order_id`
192+
);
193+
const output = await duckdbExec(sql);
194+
expect(output).toEqual([
195+
{
196+
order_id: '2',
197+
'Total Order Amount': 100,
198+
},
199+
]);
200+
});
160201
});

0 commit comments

Comments
 (0)