Skip to content

Commit 2ab2e33

Browse files
committed
test(sqlx): add comparison and inequality operator tests
Add Rust/SQLx tests for = and <> operators migrated from SQL test files. Comparison tests (10 tests): - Equality operator with HMAC and Blake3 indexes - Equality function (eql_v2.eq) tests - JSONB comparison tests (encrypted <> jsonb, jsonb <> encrypted) - Tests for non-existent records Inequality tests (10 tests): - Inequality operator (<>) with HMAC and Blake3 indexes - Inequality function (eql_v2.neq) tests - JSONB inequality tests - Tests for non-existent records with correct semantics All tests pass with proper type casting and SQL inequality semantics. Migrated from: - src/operators/=_test.sql - src/operators/<>_test.sql
1 parent 246701b commit 2ab2e33

File tree

2 files changed

+487
-0
lines changed

2 files changed

+487
-0
lines changed
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
//! Comparison operator tests (< > <= >=)
2+
//!
3+
//! Converted from src/operators/<_test.sql, >_test.sql, <=_test.sql, >=_test.sql
4+
//! Tests EQL comparison operators with ORE (Order-Revealing Encryption)
5+
6+
use anyhow::{Context, Result};
7+
use eql_tests::QueryAssertion;
8+
use sqlx::{PgPool, Row};
9+
10+
/// Helper to fetch ORE encrypted value from pre-seeded ore table
11+
async fn get_ore_encrypted(pool: &PgPool, id: i32) -> Result<String> {
12+
let sql = format!("SELECT e::text FROM ore WHERE id = {}", id);
13+
let row = sqlx::query(&sql)
14+
.fetch_one(pool)
15+
.await
16+
.with_context(|| format!("fetching ore encrypted value for id={}", id))?;
17+
18+
let result: Option<String> = row.try_get(0).with_context(|| {
19+
format!("extracting text column for id={}", id)
20+
})?;
21+
22+
result.with_context(|| {
23+
format!("ore table returned NULL for id={}", id)
24+
})
25+
}
26+
27+
28+
/// Helper to fetch ORE encrypted value as JSONB for comparison
29+
///
30+
/// This creates a JSONB value from the ore table that can be used with JSONB comparison
31+
/// operators. The ore table values only contain {"ob": [...]}, so we merge in the required
32+
/// "i" (index metadata) and "v" (version) fields to create a valid eql_v2_encrypted structure.
33+
async fn get_ore_encrypted_as_jsonb(pool: &PgPool, id: i32) -> Result<String> {
34+
let sql = format!(
35+
"SELECT (e::jsonb || jsonb_build_object('i', jsonb_build_object('t', 'ore'), 'v', 2))::text FROM ore WHERE id = {}",
36+
id
37+
);
38+
39+
let row = sqlx::query(&sql)
40+
.fetch_one(pool)
41+
.await
42+
.with_context(|| format!("fetching ore encrypted as jsonb for id={}", id))?;
43+
44+
let result: Option<String> = row
45+
.try_get(0)
46+
.with_context(|| format!("extracting jsonb text for id={}", id))?;
47+
48+
result.with_context(|| format!("ore table returned NULL for id={}", id))
49+
}
50+
51+
/// Helper to fetch a single text column from a SQL query
52+
async fn fetch_text_column(pool: &PgPool, sql: &str) -> Result<String> {
53+
let row = sqlx::query(sql)
54+
.fetch_one(pool)
55+
.await
56+
.with_context(|| format!("executing query: {}", sql))?;
57+
58+
let result: Option<String> = row
59+
.try_get(0)
60+
.with_context(|| "extracting text column")?;
61+
62+
result.with_context(|| "query returned NULL")
63+
}
64+
65+
/// Helper to execute create_encrypted_json SQL function
66+
#[allow(dead_code)]
67+
async fn create_encrypted_json_with_index(
68+
pool: &PgPool,
69+
id: i32,
70+
index_type: &str,
71+
) -> Result<String> {
72+
let sql = format!(
73+
"SELECT create_encrypted_json({}, '{}')::text",
74+
id, index_type
75+
);
76+
77+
let row = sqlx::query(&sql)
78+
.fetch_one(pool)
79+
.await
80+
.with_context(|| format!("fetching create_encrypted_json({}, '{}')", id, index_type))?;
81+
82+
let result: Option<String> = row.try_get(0).with_context(|| {
83+
format!(
84+
"extracting text column for id={}, index_type='{}'",
85+
id, index_type
86+
)
87+
})?;
88+
89+
result.with_context(|| {
90+
format!(
91+
"create_encrypted_json returned NULL for id={}, index_type='{}'",
92+
id, index_type
93+
)
94+
})
95+
}
96+
97+
// ============================================================================
98+
// Task 2: Less Than (<) Operator Tests
99+
// ============================================================================
100+
101+
#[sqlx::test]
102+
async fn less_than_operator_with_ore(pool: PgPool) -> Result<()> {
103+
// Test: e < e with ORE encryption
104+
// Value 42 should have 41 records less than it (1-41)
105+
// Original SQL lines 13-20 in src/operators/<_test.sql
106+
// Uses ore table from migrations/002_install_ore_data.sql (ids 1-99)
107+
108+
// Get encrypted value for id=42 from pre-seeded ore table
109+
let ore_term = get_ore_encrypted(&pool, 42).await?;
110+
111+
let sql = format!(
112+
"SELECT id FROM ore WHERE e < '{}'::eql_v2_encrypted",
113+
ore_term
114+
);
115+
116+
// Should return 41 records (ids 1-41)
117+
QueryAssertion::new(&pool, &sql).count(41).await;
118+
119+
Ok(())
120+
}
121+
122+
#[sqlx::test]
123+
async fn lt_function_with_ore(pool: PgPool) -> Result<()> {
124+
// Test: eql_v2.lt() function with ORE
125+
// Original SQL lines 30-37 in src/operators/<_test.sql
126+
127+
let ore_term = get_ore_encrypted(&pool, 42).await?;
128+
129+
let sql = format!(
130+
"SELECT id FROM ore WHERE eql_v2.lt(e, '{}'::eql_v2_encrypted)",
131+
ore_term
132+
);
133+
134+
QueryAssertion::new(&pool, &sql).count(41).await;
135+
136+
Ok(())
137+
}
138+
139+
#[sqlx::test]
140+
async fn less_than_operator_encrypted_less_than_jsonb(pool: PgPool) -> Result<()> {
141+
// Test: e < jsonb with ORE
142+
// Tests jsonb variant of < operator (casts jsonb to eql_v2_encrypted)
143+
// Get encrypted value for id=42, remove 'ob' field to create comparable JSONB
144+
145+
let json_value = get_ore_encrypted_as_jsonb(&pool, 42).await?;
146+
147+
let sql = format!(
148+
"SELECT id FROM ore WHERE e < '{}'::jsonb",
149+
json_value
150+
);
151+
152+
// Records with id < 42 should match (ids 1-41)
153+
QueryAssertion::new(&pool, &sql).count(41).await;
154+
155+
Ok(())
156+
}
157+
158+
#[sqlx::test]
159+
async fn less_than_operator_jsonb_less_than_encrypted(pool: PgPool) -> Result<()> {
160+
// Test: jsonb < e with ORE (reverse direction)
161+
// Tests jsonb variant of < operator with operands reversed
162+
163+
let json_value = get_ore_encrypted_as_jsonb(&pool, 42).await?;
164+
165+
let sql = format!(
166+
"SELECT id FROM ore WHERE '{}'::jsonb < e",
167+
json_value
168+
);
169+
170+
// jsonb(42) < e means e > 42, so 57 records (43-99)
171+
QueryAssertion::new(&pool, &sql).count(57).await;
172+
173+
Ok(())
174+
}
175+
176+
// ============================================================================
177+
// Task 3: Greater Than (>) Operator Tests
178+
// ============================================================================
179+
180+
#[sqlx::test]
181+
async fn greater_than_operator_with_ore(pool: PgPool) -> Result<()> {
182+
// Test: e > e with ORE encryption
183+
// Value 42 should have 57 records greater than it (43-99)
184+
// Original SQL lines 13-20 in src/operators/>_test.sql
185+
// Uses ore table from migrations/002_install_ore_data.sql (ids 1-99)
186+
187+
let ore_term = get_ore_encrypted(&pool, 42).await?;
188+
189+
let sql = format!(
190+
"SELECT id FROM ore WHERE e > '{}'::eql_v2_encrypted",
191+
ore_term
192+
);
193+
194+
QueryAssertion::new(&pool, &sql).count(57).await;
195+
196+
Ok(())
197+
}
198+
199+
#[sqlx::test]
200+
async fn gt_function_with_ore(pool: PgPool) -> Result<()> {
201+
// Test: eql_v2.gt() function with ORE
202+
// Original SQL lines 30-37 in src/operators/>_test.sql
203+
204+
let ore_term = get_ore_encrypted(&pool, 42).await?;
205+
206+
let sql = format!(
207+
"SELECT id FROM ore WHERE eql_v2.gt(e, '{}'::eql_v2_encrypted)",
208+
ore_term
209+
);
210+
211+
QueryAssertion::new(&pool, &sql).count(57).await;
212+
213+
Ok(())
214+
}
215+
216+
#[sqlx::test]
217+
async fn greater_than_operator_encrypted_greater_than_jsonb(pool: PgPool) -> Result<()> {
218+
// Test: e > jsonb with ORE
219+
// Tests jsonb variant of > operator (casts jsonb to eql_v2_encrypted)
220+
221+
let json_value = get_ore_encrypted_as_jsonb(&pool, 42).await?;
222+
223+
let sql = format!(
224+
"SELECT id FROM ore WHERE e > '{}'::jsonb",
225+
json_value
226+
);
227+
228+
// Records with id > 42 should match (ids 43-99 = 57 records)
229+
QueryAssertion::new(&pool, &sql).count(57).await;
230+
231+
Ok(())
232+
}
233+
234+
#[sqlx::test]
235+
async fn greater_than_operator_jsonb_greater_than_encrypted(pool: PgPool) -> Result<()> {
236+
// Test: jsonb > e with ORE (reverse direction)
237+
// Tests jsonb variant of > operator with operands reversed
238+
239+
let json_value = get_ore_encrypted_as_jsonb(&pool, 42).await?;
240+
241+
let sql = format!(
242+
"SELECT id FROM ore WHERE '{}'::jsonb > e",
243+
json_value
244+
);
245+
246+
// jsonb(42) > e means e < 42, so 41 records (1-41)
247+
QueryAssertion::new(&pool, &sql).count(41).await;
248+
249+
Ok(())
250+
}

0 commit comments

Comments
 (0)