Skip to content

Commit 660b1fa

Browse files
committed
Sanitise Func test cases
Change-Id: I8ee82abddc18ad94374d64091333f1fc71605761 Reviewed-on: https://review.couchbase.org/c/testrunner/+/244469 Tested-by: <pavan.pb@couchbase.com> Reviewed-by: Ajay Bhullar <ajay.bhullar@couchbase.com>
1 parent fed9092 commit 660b1fa

3 files changed

Lines changed: 192 additions & 2 deletions

File tree

conf/tuq/py-tuq-sanitize.conf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
tuqquery.tuq_sanitize.QuerySanitizeTests:
2+
test_sanitize_basic
3+
test_sanitize_dml_statements
4+
test_sanitize_complex
5+
test_sanitize_insert_upsert
6+
test_sanitize_merge
7+
test_sanitize_null_boolean
8+
test_sanitize_nested_functions
9+
test_sanitize_ddl_noop
10+
test_sanitize_dcl_noop

pytests/gsi/composite_vector_index.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3616,10 +3616,11 @@ def test_recovery_memcached_crash(self):
36163616
# Loading new documents so that persisted snapshot wouldn't have these docs
36173617
if 'RaBitQ' in self.quantization_algo_description_vector:
36183618
num_new_docs = 20
3619-
dummy_vector = [0.1] * self.dimension
3619+
description_vector = [0.1] * self.dimension
3620+
color_vector = [0.1, 0.2, 0.3]
36203621
for namespace in self.namespaces:
36213622
for i in range(num_new_docs):
3622-
insert_query = f'INSERT INTO {namespace} (KEY, VALUE) VALUES ("new_doc_{i}", {{"year": 2025, "type": "test", "rating": 3, "category": "Sedan", "fuel": "Petrol", "name": "test_car_{i}", "descriptionVector": {dummy_vector}, "colorRGBVector": {dummy_vector}}})'
3623+
insert_query = f'INSERT INTO {namespace} (KEY, VALUE) VALUES ("new_doc_{i}", {{"year": 2025, "type": "test", "rating": 3, "category": "Sedan", "fuel": "Petrol", "name": "test_car_{i}", "descriptionVector": {description_vector}, "colorRGBVector": {color_vector}}})'
36233624
self.run_cbq_query(query=insert_query, server=query_node)
36243625
self.log.info(f"Inserted {num_new_docs} docs into {namespace} via N1QL")
36253626
else:

pytests/tuqquery/tuq_sanitize.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
from .tuq import QueryTests
2+
3+
4+
class QuerySanitizeTests(QueryTests):
5+
def setUp(self):
6+
super(QuerySanitizeTests, self).setUp()
7+
self.bucket = "default"
8+
9+
def suite_setUp(self):
10+
super(QuerySanitizeTests, self).suite_setUp()
11+
12+
def tearDown(self):
13+
super(QuerySanitizeTests, self).tearDown()
14+
15+
def suite_tearDown(self):
16+
super(QuerySanitizeTests, self).suite_tearDown()
17+
18+
def test_sanitize_basic(self):
19+
"""
20+
MB-68699: Test SANITIZE function with basic SELECT statements.
21+
"""
22+
result = self.run_cbq_query('SELECT SANITIZE("SELECT * FROM default WHERE name = \\"xyz\\" AND age > 10") AS sanitized')
23+
sanitized = result['results'][0]['sanitized']
24+
self.log.info(f"Sanitized output: {sanitized}")
25+
self.assertIn('statement', sanitized)
26+
self.assertIn('parametersMap', sanitized)
27+
self.assertTrue(len(sanitized['parametersMap']) >= 2,
28+
f"Expected at least 2 parameters, got {sanitized['parametersMap']}")
29+
self.assertNotIn('"xyz"', sanitized['statement'])
30+
self.assertIn('$_n', sanitized['statement'])
31+
param_values = list(sanitized['parametersMap'].values())
32+
self.assertIn('xyz', param_values)
33+
self.assertIn(10, param_values)
34+
35+
# SELECT with no literals
36+
result = self.run_cbq_query('SELECT SANITIZE("SELECT * FROM default") AS sanitized')
37+
sanitized = result['results'][0]['sanitized']
38+
self.log.info(f"Sanitized (no literals): {sanitized}")
39+
self.assertIn('statement', sanitized)
40+
params = sanitized.get('parametersMap') or {}
41+
self.assertEqual(len(params), 0,
42+
f"Expected empty/null parametersMap, got {sanitized.get('parametersMap')}")
43+
44+
def test_sanitize_dml_statements(self):
45+
"""
46+
MB-68699: Test SANITIZE function with DML statements (UPDATE, DELETE).
47+
"""
48+
result = self.run_cbq_query('SELECT SANITIZE("UPDATE default SET name = \\"new_name\\" WHERE id = 5") AS sanitized')
49+
sanitized = result['results'][0]['sanitized']
50+
self.log.info(f"Sanitized UPDATE: {sanitized}")
51+
param_values = list(sanitized['parametersMap'].values())
52+
self.assertIn('new_name', param_values)
53+
self.assertIn(5, param_values)
54+
self.assertNotIn('"new_name"', sanitized['statement'])
55+
56+
result = self.run_cbq_query('SELECT SANITIZE("DELETE FROM default WHERE status = \\"inactive\\"") AS sanitized')
57+
sanitized = result['results'][0]['sanitized']
58+
self.log.info(f"Sanitized DELETE: {sanitized}")
59+
param_values = list(sanitized['parametersMap'].values())
60+
self.assertIn('inactive', param_values)
61+
62+
def test_sanitize_complex(self):
63+
"""
64+
MB-68699: Test SANITIZE with subqueries, nested expressions, and multiple same values.
65+
"""
66+
result = self.run_cbq_query('SELECT SANITIZE("SELECT * FROM default d1 WHERE id IN (SELECT RAW id FROM default d2 WHERE x = 100)") AS sanitized')
67+
sanitized = result['results'][0]['sanitized']
68+
self.log.info(f"Sanitized subquery: {sanitized}")
69+
param_values = list(sanitized['parametersMap'].values())
70+
self.assertIn(100, param_values)
71+
72+
result = self.run_cbq_query('SELECT SANITIZE("SELECT c1, {c1, \\"o1\\":{c2, \\"c3\\": \\"abc\\", \\"c4\\": abs(c2)}} AS o FROM default AS d WHERE c1 = 10") AS sanitized')
73+
sanitized = result['results'][0]['sanitized']
74+
self.log.info(f"Sanitized array/object: {sanitized}")
75+
param_values = list(sanitized['parametersMap'].values())
76+
self.assertIn('abc', param_values)
77+
self.assertIn(10, param_values)
78+
79+
result = self.run_cbq_query('SELECT SANITIZE("SELECT * FROM default WHERE a = \\"x\\" AND b = \\"x\\"") AS sanitized')
80+
sanitized = result['results'][0]['sanitized']
81+
self.log.info(f"Sanitized duplicate values: {sanitized}")
82+
self.assertTrue(len(sanitized['parametersMap']) >= 2,
83+
f"Each literal should get its own parameter, got {sanitized['parametersMap']}")
84+
85+
def test_sanitize_insert_upsert(self):
86+
"""
87+
MB-68699: Test SANITIZE with INSERT and UPSERT statements.
88+
"""
89+
result = self.run_cbq_query('SELECT SANITIZE("INSERT INTO default VALUES (\\"k1\\", {\\"name\\": \\"abc\\"})") AS sanitized')
90+
sanitized = result['results'][0]['sanitized']
91+
self.log.info(f"Sanitized INSERT: {sanitized}")
92+
self.assertIn('statement', sanitized)
93+
self.assertIn('parametersMap', sanitized)
94+
param_values = list(sanitized['parametersMap'].values())
95+
self.assertIn('k1', param_values)
96+
self.assertTrue(any(isinstance(v, dict) and v.get('name') == 'abc' for v in param_values),
97+
f"Expected object with name=abc in params, got {param_values}")
98+
99+
result = self.run_cbq_query('SELECT SANITIZE("UPSERT INTO default VALUES (\\"k2\\", {\\"x\\": 1})") AS sanitized')
100+
sanitized = result['results'][0]['sanitized']
101+
self.log.info(f"Sanitized UPSERT: {sanitized}")
102+
param_values = list(sanitized['parametersMap'].values())
103+
self.assertIn('k2', param_values)
104+
self.assertTrue(any(isinstance(v, dict) and v.get('x') == 1 for v in param_values),
105+
f"Expected object with x=1 in params, got {param_values}")
106+
107+
def test_sanitize_merge(self):
108+
"""
109+
MB-68699: Test SANITIZE with MERGE statement.
110+
"""
111+
result = self.run_cbq_query('SELECT SANITIZE("MERGE INTO default t1 USING default t2 ON t1.id = t2.id WHEN MATCHED THEN UPDATE SET t1.v = \\"val\\"") AS sanitized')
112+
sanitized = result['results'][0]['sanitized']
113+
self.log.info(f"Sanitized MERGE: {sanitized}")
114+
self.assertIn('statement', sanitized)
115+
self.assertIn('parametersMap', sanitized)
116+
param_values = list(sanitized['parametersMap'].values())
117+
self.assertIn('val', param_values)
118+
119+
def test_sanitize_null_boolean(self):
120+
"""
121+
MB-68699: Test SANITIZE with NULL, true, false literals.
122+
"""
123+
result = self.run_cbq_query('SELECT SANITIZE("SELECT * FROM default WHERE a IS NULL AND b = true AND c = false") AS sanitized')
124+
sanitized = result['results'][0]['sanitized']
125+
self.log.info(f"Sanitized NULL/boolean: {sanitized}")
126+
self.assertIn('statement', sanitized)
127+
self.assertIn('parametersMap', sanitized)
128+
129+
def test_sanitize_nested_functions(self):
130+
"""
131+
MB-68699: Test SANITIZE with literals inside nested functions.
132+
"""
133+
result = self.run_cbq_query('SELECT SANITIZE("SELECT * FROM default WHERE LOWER(name) = \\"test\\" AND ABS(val) > 5.5") AS sanitized')
134+
sanitized = result['results'][0]['sanitized']
135+
self.log.info(f"Sanitized nested functions: {sanitized}")
136+
self.assertIn('statement', sanitized)
137+
param_values = list(sanitized['parametersMap'].values())
138+
self.assertIn('test', param_values)
139+
self.assertIn(5.5, param_values)
140+
141+
def test_sanitize_ddl_noop(self):
142+
"""
143+
MB-68699: Verify DDL statements (CREATE INDEX, CREATE FUNCTION) are not sanitized.
144+
"""
145+
result = self.run_cbq_query('SELECT SANITIZE("CREATE INDEX idx1 ON default(name)") AS sanitized')
146+
sanitized = result['results'][0]['sanitized']
147+
self.log.info(f"Sanitized CREATE INDEX: {sanitized}")
148+
self.assertIn('statement', sanitized)
149+
params = sanitized.get('parametersMap') or {}
150+
self.assertEqual(len(params), 0,
151+
f"DDL CREATE INDEX should not be sanitized, got {params}")
152+
153+
result = self.run_cbq_query('SELECT SANITIZE("DROP INDEX idx1 ON default") AS sanitized')
154+
sanitized = result['results'][0]['sanitized']
155+
self.log.info(f"Sanitized DROP INDEX: {sanitized}")
156+
self.assertIn('statement', sanitized)
157+
params = sanitized.get('parametersMap') or {}
158+
self.assertEqual(len(params), 0,
159+
f"DDL DROP INDEX should not be sanitized, got {params}")
160+
161+
def test_sanitize_dcl_noop(self):
162+
"""
163+
MB-68699: Verify DCL statements (GRANT, REVOKE) are not sanitized.
164+
"""
165+
result = self.run_cbq_query('SELECT SANITIZE("GRANT query_select ON default TO user1") AS sanitized')
166+
sanitized = result['results'][0]['sanitized']
167+
self.log.info(f"Sanitized GRANT: {sanitized}")
168+
self.assertIn('statement', sanitized)
169+
params = sanitized.get('parametersMap') or {}
170+
self.assertEqual(len(params), 0,
171+
f"DCL GRANT should not be sanitized, got {params}")
172+
173+
result = self.run_cbq_query('SELECT SANITIZE("REVOKE query_select ON default FROM user1") AS sanitized')
174+
sanitized = result['results'][0]['sanitized']
175+
self.log.info(f"Sanitized REVOKE: {sanitized}")
176+
self.assertIn('statement', sanitized)
177+
params = sanitized.get('parametersMap') or {}
178+
self.assertEqual(len(params), 0,
179+
f"DCL REVOKE should not be sanitized, got {params}")

0 commit comments

Comments
 (0)