Skip to content

Commit 9982207

Browse files
authored
chore: sql string sanitize (#155)
* add: string sql sanitize * add: test * add: test * add: test
1 parent d724e27 commit 9982207

File tree

8 files changed

+229
-6
lines changed

8 files changed

+229
-6
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.99",
3+
"version": "0.0.100",
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.99",
3+
"version": "0.0.100",
44
"dependencies": {
55
"tslib": "^2.3.0"
66
},
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { Dimension, Measure } from '../../types/cube-types';
2+
import { CUBE_TYPE_TO_DUCKDB_TYPE } from '../../utils/cube-type-to-duckdb-type';
3+
import { valueBuilder } from './base-condition-builder';
4+
5+
describe('valueBuilder', () => {
6+
it('should build a value for a string type', () => {
7+
const memberInfo: Measure | Dimension = {
8+
name: 'test',
9+
type: 'string',
10+
sql: 'test',
11+
};
12+
const value = 'test_value';
13+
const result = valueBuilder(value, memberInfo);
14+
expect(result).toEqual({
15+
type: {
16+
id: CUBE_TYPE_TO_DUCKDB_TYPE.string,
17+
type_info: null,
18+
},
19+
is_null: false,
20+
value: value,
21+
});
22+
});
23+
24+
it('should build a value for a string type with single quotes', () => {
25+
const memberInfo: Measure | Dimension = {
26+
name: 'test',
27+
type: 'string',
28+
sql: 'test',
29+
};
30+
31+
const value = "I'm a string with a single quote";
32+
const result = valueBuilder(value, memberInfo);
33+
expect(result).toEqual({
34+
type: {
35+
id: CUBE_TYPE_TO_DUCKDB_TYPE.string,
36+
type_info: null,
37+
},
38+
is_null: false,
39+
value: "I''m a string with a single quote",
40+
});
41+
});
42+
43+
it('should build a value for a number type', () => {
44+
const memberInfo: Measure | Dimension = {
45+
name: 'test',
46+
type: 'number',
47+
sql: 'test',
48+
};
49+
const value = '123';
50+
const result = valueBuilder(value, memberInfo);
51+
52+
expect(result).toEqual({
53+
type: {
54+
id: CUBE_TYPE_TO_DUCKDB_TYPE.number,
55+
type_info: {
56+
alias: '',
57+
type: 'DECIMAL_TYPE_INFO',
58+
width: 3,
59+
scale: 0,
60+
},
61+
},
62+
is_null: false,
63+
value: 123,
64+
});
65+
});
66+
67+
it('should build a value for a boolean type', () => {
68+
const memberInfo: Measure | Dimension = {
69+
name: 'test',
70+
type: 'boolean',
71+
sql: 'test',
72+
};
73+
const value = 'true';
74+
const result = valueBuilder(value, memberInfo);
75+
expect(result).toEqual({
76+
type: {
77+
id: CUBE_TYPE_TO_DUCKDB_TYPE.boolean,
78+
type_info: null,
79+
},
80+
is_null: false,
81+
value: true,
82+
});
83+
});
84+
85+
it('should build a value for a time type', () => {
86+
const memberInfo: Measure | Dimension = {
87+
name: 'test',
88+
type: 'time',
89+
sql: 'test',
90+
};
91+
const value = '2023-01-01';
92+
const result = valueBuilder(value, memberInfo);
93+
expect(result).toEqual({
94+
type: {
95+
id: CUBE_TYPE_TO_DUCKDB_TYPE.time,
96+
type_info: null,
97+
},
98+
is_null: false,
99+
value: value,
100+
});
101+
});
102+
103+
it('should default to string for unknown types', () => {
104+
const memberInfo: Measure | Dimension = {
105+
name: 'test',
106+
type: 'unknown' as any,
107+
sql: 'test',
108+
};
109+
const value = 'test_value';
110+
const result = valueBuilder(value, memberInfo);
111+
expect(result).toEqual({
112+
type: {
113+
id: CUBE_TYPE_TO_DUCKDB_TYPE.string,
114+
type_info: null,
115+
},
116+
is_null: false,
117+
value: value,
118+
});
119+
});
120+
});

meerkat-core/src/cube-filter-transformer/base-condition-builder/base-condition-builder.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Dimension, Measure } from '../../types/cube-types/index';
22

33
import { COLUMN_NAME_DELIMITER } from '../../member-formatters/constants';
4+
import { sanitizeStringValue } from '../../member-formatters/sanitize-value';
45
import {
56
ExpressionClass,
67
ExpressionType,
@@ -115,7 +116,7 @@ export const valueBuilder = (
115116
type_info: null,
116117
},
117118
is_null: false,
118-
value: value,
119+
value: sanitizeStringValue(value),
119120
};
120121

121122
case 'number': {
@@ -157,7 +158,7 @@ export const valueBuilder = (
157158
type_info: null,
158159
},
159160
is_null: false,
160-
value: value,
161+
value: sanitizeStringValue(value),
161162
};
162163
}
163164
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { sanitizeStringValue } from '../sanitize-value';
2+
3+
describe('sanitizeStringValue', () => {
4+
it('should escape single quotes', () => {
5+
expect(sanitizeStringValue("I'm a string")).toBe("I''m a string");
6+
});
7+
8+
it('should escape double quotes', () => {
9+
expect(sanitizeStringValue('I"m a string')).toBe('I"m a string');
10+
});
11+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Sanitizes a string value for use in a SQL query
3+
*/
4+
export const sanitizeStringValue = (value: string) => {
5+
return value.replace(/'/g, "''");
6+
};

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

meerkat-node/src/__tests__/test-data.ts

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ INSERT INTO orders VALUES
2222
(9, '5', '2', '2022-05-05', 85, []),
2323
(10, '6', '3', '2022-06-01', 120, ['myntra', 'amazon']),
2424
(11, '6aa6', '3', '2024-06-01', 0, ['amazon']),
25-
(12, NULL, '3', '2024-07-01', 100, ['flipkart']);
25+
(12, NULL, '3', '2024-07-01', 100, ['flipkart']),
26+
(13, '7', '6', '2024-08-01', 100, ['swiggy''s']);
2627
`;
2728

2829
export const TABLE_SCHEMA = {
@@ -119,6 +120,10 @@ export const TEST_DATA = [
119120
orders__customer_id: '3',
120121
orders__total_order_amount: 100,
121122
},
123+
{
124+
orders__customer_id: '7',
125+
orders__total_order_amount: 100,
126+
},
122127
{
123128
orders__customer_id: null,
124129
orders__total_order_amount: 100,
@@ -292,6 +297,15 @@ export const TEST_DATA = [
292297
order_amount: 0.0,
293298
vendors: ['amazon'],
294299
},
300+
{
301+
order_id: 13,
302+
customer_id: '7',
303+
orders__customer_id: '7',
304+
product_id: '6',
305+
order_date: '2024-08-01',
306+
order_amount: 100.0,
307+
vendors: ["swiggy's"],
308+
},
295309
],
296310
},
297311
],
@@ -377,6 +391,16 @@ export const TEST_DATA = [
377391
order_amount: 120,
378392
vendors: ['myntra', 'amazon'],
379393
},
394+
{
395+
customer_id: '7',
396+
order_amount: 100,
397+
order_date: '2024-08-01T00:00:00.000Z',
398+
order_id: 13,
399+
orders__customer_id: '7',
400+
orders__order_date: undefined,
401+
product_id: '6',
402+
vendors: ["swiggy's"],
403+
},
380404
],
381405
},
382406
],
@@ -469,6 +493,16 @@ export const TEST_DATA = [
469493
product_id: '3',
470494
vendors: ['flipkart'],
471495
},
496+
{
497+
customer_id: '7',
498+
order_amount: 100,
499+
order_date: '2024-08-01T00:00:00.000Z',
500+
order_id: 13,
501+
orders__order_amount: 100,
502+
orders__order_date: undefined,
503+
product_id: '6',
504+
vendors: ["swiggy's"],
505+
},
472506
],
473507
},
474508
],
@@ -661,6 +695,15 @@ export const TEST_DATA = [
661695
product_id: '3',
662696
vendors: ['flipkart'],
663697
},
698+
{
699+
customer_id: '7',
700+
order_amount: 100,
701+
order_date: '2024-08-01T00:00:00.000Z',
702+
order_id: 13,
703+
orders__order_date: '2024-08-01T00:00:00.000Z',
704+
product_id: '6',
705+
vendors: ["swiggy's"],
706+
},
664707
],
665708
},
666709
],
@@ -989,6 +1032,37 @@ export const TEST_DATA = [
9891032
},
9901033
],
9911034
},
1035+
{
1036+
testName: 'In with single quotes',
1037+
expectedSQL: `SELECT orders.* FROM (SELECT *, vendors AS orders__vendors FROM (select * from orders) AS orders) AS orders WHERE ((orders__vendors && (ARRAY['swiggy''s'])))`,
1038+
cubeInput: {
1039+
measures: ['*'],
1040+
filters: [
1041+
{
1042+
and: [
1043+
{
1044+
member: 'orders.vendors',
1045+
operator: 'in',
1046+
values: ["swiggy's"],
1047+
},
1048+
],
1049+
},
1050+
],
1051+
dimensions: [],
1052+
},
1053+
expectedOutput: [
1054+
{
1055+
customer_id: '7',
1056+
order_amount: 100,
1057+
order_date: '2024-08-01T00:00:00.000Z',
1058+
order_id: 13,
1059+
orders__order_date: undefined,
1060+
orders__vendors: ["swiggy's"],
1061+
product_id: '6',
1062+
vendors: ["swiggy's"],
1063+
},
1064+
],
1065+
},
9921066
],
9931067
[
9941068
{
@@ -1048,6 +1122,17 @@ export const TEST_DATA = [
10481122
product_id: '3',
10491123
vendors: ['amazon'],
10501124
},
1125+
{
1126+
customer_id: '7',
1127+
order_amount: 100,
1128+
order_date: '2024-08-01T00:00:00.000Z',
1129+
order_id: 13,
1130+
orders__customer_id: '7',
1131+
orders__order_date: undefined,
1132+
orders__vendors: ["swiggy's"],
1133+
product_id: '6',
1134+
vendors: ["swiggy's"],
1135+
},
10511136
],
10521137
},
10531138
],

0 commit comments

Comments
 (0)