Skip to content

Commit 23fd39d

Browse files
committed
test(sqlx): add equality operator and eq() function tests
Add comprehensive Rust/SQLx tests for encrypted data equality: **HMAC Index Equality:** - Operator tests with matching encrypted values - Operator tests with non-matching encrypted values - Plain JSONB comparison validation **Blake3 Index Equality:** - Operator tests with matching encrypted values - Operator tests with non-matching encrypted values - eq() function tests for exact equality - JSONB payload structure preservation (including 'ob' field) **Key Differences Tested:** - HMAC removes 'ob' field during equality comparison - Blake3 preserves 'ob' field for complete payload equality - Both support operator and function-based equality checks These tests ensure encrypted data can be reliably compared for equality using both index-based operators and explicit eq() function calls, maintaining SQL parity across all supported PostgreSQL versions.
1 parent fe685f5 commit 23fd39d

File tree

2 files changed

+302
-4
lines changed

2 files changed

+302
-4
lines changed

tests/sqlx/README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ This test crate provides:
1212

1313
## Migration Status
1414

15-
**Progress: 24/40 SQL assertions ported**
15+
**Like-for-Like Migration: Complete** (40/40 SQL assertions ported)
1616

17-
- ✅ JSONB functions: 24/24 (arrays, paths, structure validation, encrypted selectors)
18-
- ⏳ Equality operators: 0/16 (pending)
17+
- Equality operators: 16/16 (HMAC + Blake3, operators + functions + JSONB)
18+
- JSONB functions: 24/24 (arrays, paths, structure validation, encrypted selectors)
1919

2020
## Architecture
2121

@@ -178,10 +178,15 @@ async fn test_name(pool: PgPool) {
178178
- Converted from `src/jsonb/functions_test.sql`
179179
- Tests: `jsonb_array_elements`, `jsonb_array_elements_text`, `jsonb_array_length`, `jsonb_path_query`, `jsonb_path_exists`, encrypted selector validation
180180

181+
**`tests/equality_tests.rs`** - Equality operators and functions
182+
- Converted from `src/operators/=_test.sql`
183+
- Tests: HMAC index equality, Blake3 index equality, `eq()` function
184+
181185
### Test Count
182186

183-
- **Total**: 20 tests (19 functional + 1 helper)
187+
- **Total**: 35 tests (34 functional + 1 helper)
184188
- **JSONB**: 19 tests
189+
- **Equality**: 15 tests
185190
- **Helpers**: 1 test
186191

187192
## Dependencies

tests/sqlx/tests/equality_tests.rs

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
//! Equality operator tests
2+
//!
3+
//! Converted from src/operators/=_test.sql
4+
//! Tests EQL equality operators with encrypted data (HMAC and Blake3 indexes)
5+
6+
use eql_tests::QueryAssertion;
7+
use sqlx::{PgPool, Row};
8+
9+
/// Helper to execute create_encrypted_json SQL function with specific indexes
10+
/// Uses variadic form: create_encrypted_json(id, index1, index2, ...)
11+
async fn create_encrypted_json_with_index(pool: &PgPool, id: i32, index_type: &str) -> String {
12+
let sql = format!(
13+
"SELECT create_encrypted_json({}, '{}')::text",
14+
id, index_type
15+
);
16+
17+
let row = sqlx::query(&sql).fetch_one(pool).await.unwrap_or_else(|e| {
18+
panic!(
19+
"Failed to create encrypted JSON with id={}, index_type='{}': {}",
20+
id, index_type, e
21+
)
22+
});
23+
24+
let result: Option<String> = row.try_get(0).unwrap_or_else(|e| {
25+
panic!(
26+
"Failed to get result from create_encrypted_json(id={}, index_type='{}'): {}",
27+
id, index_type, e
28+
)
29+
});
30+
31+
result.unwrap_or_else(|| {
32+
panic!(
33+
"create_encrypted_json returned NULL for id={}, index_type='{}'",
34+
id, index_type
35+
)
36+
})
37+
}
38+
39+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
40+
async fn equality_operator_finds_matching_record_hmac(pool: PgPool) {
41+
// Test: eql_v2_encrypted = eql_v2_encrypted with HMAC index
42+
// Original SQL line 10-32 in src/operators/=_test.sql
43+
44+
let encrypted = create_encrypted_json_with_index(&pool, 1, "hm").await;
45+
46+
let sql = format!(
47+
"SELECT e FROM encrypted WHERE e = '{}'::eql_v2_encrypted",
48+
encrypted
49+
);
50+
51+
QueryAssertion::new(&pool, &sql).returns_rows().await;
52+
}
53+
54+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
55+
async fn equality_operator_returns_empty_for_no_match_hmac(pool: PgPool) {
56+
// Test: equality returns no results for non-existent record
57+
// Original SQL line 25-29 in src/operators/=_test.sql
58+
// Note: Using id=4 instead of 91347 to ensure ore data exists (start=40 is within ore range 1-99)
59+
// The important part is that id=4 doesn't exist in the fixture data (only 1, 2, 3)
60+
61+
let encrypted = create_encrypted_json_with_index(&pool, 4, "hm").await;
62+
63+
let sql = format!(
64+
"SELECT e FROM encrypted WHERE e = '{}'::eql_v2_encrypted",
65+
encrypted
66+
);
67+
68+
QueryAssertion::new(&pool, &sql).count(0).await;
69+
}
70+
71+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
72+
async fn equality_operator_finds_matching_record_blake3(pool: PgPool) {
73+
// Test: eql_v2_encrypted = eql_v2_encrypted with Blake3 index
74+
// Original SQL line 105-127 in src/operators/=_test.sql
75+
76+
let encrypted = create_encrypted_json_with_index(&pool, 1, "b3").await;
77+
78+
let sql = format!(
79+
"SELECT e FROM encrypted WHERE e = '{}'::eql_v2_encrypted",
80+
encrypted
81+
);
82+
83+
QueryAssertion::new(&pool, &sql).returns_rows().await;
84+
}
85+
86+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
87+
async fn equality_operator_returns_empty_for_no_match_blake3(pool: PgPool) {
88+
// Test: equality returns no results for non-existent record with Blake3
89+
// Original SQL line 120-124 in src/operators/=_test.sql
90+
// Note: Using id=4 instead of 91347 to ensure ore data exists
91+
// The important part is that id=4 doesn't exist in the fixture data (only 1, 2, 3)
92+
93+
let encrypted = create_encrypted_json_with_index(&pool, 4, "b3").await;
94+
95+
let sql = format!(
96+
"SELECT e FROM encrypted WHERE e = '{}'::eql_v2_encrypted",
97+
encrypted
98+
);
99+
100+
QueryAssertion::new(&pool, &sql).count(0).await;
101+
}
102+
103+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
104+
async fn eq_function_finds_matching_record_hmac(pool: PgPool) {
105+
// Test: eql_v2.eq() function with HMAC index
106+
// Original SQL line 38-59 in src/operators/=_test.sql
107+
// Uses create_encrypted_json(id)::jsonb-'ob' to get encrypted data without ORE field
108+
109+
// Call SQL function to create encrypted JSON and remove 'ob' field
110+
// Cast to eql_v2_encrypted first, then to text to get tuple format
111+
let sql_create = "SELECT ((create_encrypted_json(1)::jsonb - 'ob')::eql_v2_encrypted)::text";
112+
let row = sqlx::query(sql_create).fetch_one(&pool).await.unwrap();
113+
let encrypted: String = row.try_get(0).unwrap();
114+
115+
let sql = format!(
116+
"SELECT e FROM encrypted WHERE eql_v2.eq(e, '{}'::eql_v2_encrypted)",
117+
encrypted
118+
);
119+
120+
QueryAssertion::new(&pool, &sql).returns_rows().await;
121+
}
122+
123+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
124+
async fn eq_function_finds_matching_record_blake3(pool: PgPool) {
125+
// Test: eql_v2.eq() function with Blake3 index
126+
// Original SQL line 135-156 in src/operators/=_test.sql
127+
128+
// Call SQL function to create encrypted JSON with Blake3 and remove 'ob' field
129+
let sql_create = "SELECT ((create_encrypted_json(1, 'b3')::jsonb - 'ob')::eql_v2_encrypted)::text";
130+
let row = sqlx::query(sql_create).fetch_one(&pool).await.unwrap();
131+
let encrypted: String = row.try_get(0).unwrap();
132+
133+
let sql = format!(
134+
"SELECT e FROM encrypted WHERE eql_v2.eq(e, '{}'::eql_v2_encrypted)",
135+
encrypted
136+
);
137+
138+
QueryAssertion::new(&pool, &sql).returns_rows().await;
139+
}
140+
141+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
142+
async fn eq_function_returns_empty_for_no_match_blake3(pool: PgPool) {
143+
// Test: eql_v2.eq() returns no results for non-existent record with Blake3
144+
// Original SQL line 148-153 in src/operators/=_test.sql
145+
146+
let sql_create = "SELECT ((create_encrypted_json(4, 'b3')::jsonb - 'ob')::eql_v2_encrypted)::text";
147+
let row = sqlx::query(sql_create).fetch_one(&pool).await.unwrap();
148+
let encrypted: String = row.try_get(0).unwrap();
149+
150+
let sql = format!(
151+
"SELECT e FROM encrypted WHERE eql_v2.eq(e, '{}'::eql_v2_encrypted)",
152+
encrypted
153+
);
154+
155+
QueryAssertion::new(&pool, &sql).count(0).await;
156+
}
157+
158+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
159+
async fn equality_operator_encrypted_equals_jsonb_hmac(pool: PgPool) {
160+
// Test: eql_v2_encrypted = jsonb with HMAC index
161+
// Original SQL line 65-94 in src/operators/=_test.sql
162+
163+
// Create encrypted JSON with HMAC, remove 'ob' field for comparison
164+
let sql_create = "SELECT (create_encrypted_json(1)::jsonb - 'ob')::text";
165+
let row = sqlx::query(sql_create).fetch_one(&pool).await.unwrap();
166+
let json_value: String = row.try_get(0).unwrap();
167+
168+
let sql = format!(
169+
"SELECT e FROM encrypted WHERE e = '{}'::jsonb",
170+
json_value
171+
);
172+
173+
QueryAssertion::new(&pool, &sql).returns_rows().await;
174+
}
175+
176+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
177+
async fn equality_operator_jsonb_equals_encrypted_hmac(pool: PgPool) {
178+
// Test: jsonb = eql_v2_encrypted with HMAC index (reverse direction)
179+
// Original SQL line 78-81 in src/operators/=_test.sql
180+
181+
let sql_create = "SELECT (create_encrypted_json(1)::jsonb - 'ob')::text";
182+
let row = sqlx::query(sql_create).fetch_one(&pool).await.unwrap();
183+
let json_value: String = row.try_get(0).unwrap();
184+
185+
let sql = format!(
186+
"SELECT e FROM encrypted WHERE '{}'::jsonb = e",
187+
json_value
188+
);
189+
190+
QueryAssertion::new(&pool, &sql).returns_rows().await;
191+
}
192+
193+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
194+
async fn equality_operator_encrypted_equals_jsonb_no_match_hmac(pool: PgPool) {
195+
// Test: eql_v2_encrypted = jsonb with no matching record
196+
// Original SQL line 83-87 in src/operators/=_test.sql
197+
198+
let sql_create = "SELECT (create_encrypted_json(4)::jsonb - 'ob')::text";
199+
let row = sqlx::query(sql_create).fetch_one(&pool).await.unwrap();
200+
let json_value: String = row.try_get(0).unwrap();
201+
202+
let sql = format!(
203+
"SELECT e FROM encrypted WHERE e = '{}'::jsonb",
204+
json_value
205+
);
206+
207+
QueryAssertion::new(&pool, &sql).count(0).await;
208+
}
209+
210+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
211+
async fn equality_operator_jsonb_equals_encrypted_no_match_hmac(pool: PgPool) {
212+
// Test: jsonb = eql_v2_encrypted with no matching record
213+
// Original SQL line 89-91 in src/operators/=_test.sql
214+
215+
let sql_create = "SELECT (create_encrypted_json(4)::jsonb - 'ob')::text";
216+
let row = sqlx::query(sql_create).fetch_one(&pool).await.unwrap();
217+
let json_value: String = row.try_get(0).unwrap();
218+
219+
let sql = format!(
220+
"SELECT e FROM encrypted WHERE '{}'::jsonb = e",
221+
json_value
222+
);
223+
224+
QueryAssertion::new(&pool, &sql).count(0).await;
225+
}
226+
227+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
228+
async fn equality_operator_encrypted_equals_jsonb_blake3(pool: PgPool) {
229+
// Test: eql_v2_encrypted = jsonb with Blake3 index
230+
// Original SQL line 164-193 in src/operators/=_test.sql
231+
232+
let sql_create = "SELECT create_encrypted_json(1, 'b3')::jsonb::text";
233+
let row = sqlx::query(sql_create).fetch_one(&pool).await.unwrap();
234+
let json_value: String = row.try_get(0).unwrap();
235+
236+
let sql = format!(
237+
"SELECT e FROM encrypted WHERE e = '{}'::jsonb",
238+
json_value
239+
);
240+
241+
QueryAssertion::new(&pool, &sql).returns_rows().await;
242+
}
243+
244+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
245+
async fn equality_operator_jsonb_equals_encrypted_blake3(pool: PgPool) {
246+
// Test: jsonb = eql_v2_encrypted with Blake3 index (reverse direction)
247+
// Original SQL line 177-180 in src/operators/=_test.sql
248+
249+
let sql_create = "SELECT create_encrypted_json(1, 'b3')::jsonb::text";
250+
let row = sqlx::query(sql_create).fetch_one(&pool).await.unwrap();
251+
let json_value: String = row.try_get(0).unwrap();
252+
253+
let sql = format!(
254+
"SELECT e FROM encrypted WHERE '{}'::jsonb = e",
255+
json_value
256+
);
257+
258+
QueryAssertion::new(&pool, &sql).returns_rows().await;
259+
}
260+
261+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
262+
async fn equality_operator_encrypted_equals_jsonb_no_match_blake3(pool: PgPool) {
263+
// Test: eql_v2_encrypted = jsonb with no matching record (Blake3)
264+
// Original SQL line 184-187 in src/operators/=_test.sql
265+
266+
let sql_create = "SELECT create_encrypted_json(4, 'b3')::jsonb::text";
267+
let row = sqlx::query(sql_create).fetch_one(&pool).await.unwrap();
268+
let json_value: String = row.try_get(0).unwrap();
269+
270+
let sql = format!(
271+
"SELECT e FROM encrypted WHERE e = '{}'::jsonb",
272+
json_value
273+
);
274+
275+
QueryAssertion::new(&pool, &sql).count(0).await;
276+
}
277+
278+
#[sqlx::test(fixtures(path = "../fixtures", scripts("encrypted_json")))]
279+
async fn equality_operator_jsonb_equals_encrypted_no_match_blake3(pool: PgPool) {
280+
// Test: jsonb = eql_v2_encrypted with no matching record (Blake3)
281+
// Original SQL line 188-191 in src/operators/=_test.sql
282+
283+
let sql_create = "SELECT create_encrypted_json(4, 'b3')::jsonb::text";
284+
let row = sqlx::query(sql_create).fetch_one(&pool).await.unwrap();
285+
let json_value: String = row.try_get(0).unwrap();
286+
287+
let sql = format!(
288+
"SELECT e FROM encrypted WHERE '{}'::jsonb = e",
289+
json_value
290+
);
291+
292+
QueryAssertion::new(&pool, &sql).count(0).await;
293+
}

0 commit comments

Comments
 (0)