-
Notifications
You must be signed in to change notification settings - Fork 1
feat(proxy): support OPE index type #390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6e27e0f
b27898d
17d9de7
69123e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -201,7 +201,7 @@ This statement adds a `unique` index for the `email` column in the `users` table | |
|
|
||
| `unique` indexes are used to find records with columns with unique values, like with the `=` operator. | ||
|
|
||
| There are two other types of encrypted indexes you can use on `text` data: | ||
| There are other types of encrypted indexes you can use on `text` data: | ||
|
|
||
| ```sql | ||
| SELECT eql_v2.add_search_config( | ||
|
|
@@ -217,10 +217,18 @@ SELECT eql_v2.add_search_config( | |
| 'ore', | ||
| 'text' | ||
| ); | ||
|
|
||
| SELECT eql_v2.add_search_config( | ||
| 'users', | ||
| 'email', | ||
| 'ope', | ||
| 'text' | ||
| ); | ||
| ``` | ||
|
|
||
| The first SQL statement adds a `match` index, which is used for partial matches with `LIKE`. | ||
| The second SQL statement adds an `ore` index, which is used for ordering with `ORDER BY`. | ||
| The second SQL statement adds an `ore` index, which is used for ordering with `ORDER BY` and range comparisons (`<`, `<=`, `>`, `>=`). | ||
| The third SQL statement adds an `ope` index, which supports the same range and ordering operators as `ore` and is a drop-in alternative — pick `ope` or `ore` per column, not both. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add selection criteria for choosing between The documentation states that users should "pick Consider adding a brief explanation of the key differences or a link to more detailed documentation that covers:
🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| > ![IMPORTANT] | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -88,6 +88,18 @@ pub async fn clear_with_client(client: &Client) { | |||||||||||||||||||||||||||||
| client.simple_query(sql).await.unwrap(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| /// Truncate a single table by name. Useful for tests that own a dedicated | ||||||||||||||||||||||||||||||
| /// fixture table (e.g. the per-test ORE/OPE tables) and don't need to wipe | ||||||||||||||||||||||||||||||
| /// the shared `encrypted`/`plaintext` tables. | ||||||||||||||||||||||||||||||
| pub async fn clear_table_with_client(client: &Client, table: &str) { | ||||||||||||||||||||||||||||||
| let sql = format!("TRUNCATE {}", table); | ||||||||||||||||||||||||||||||
| client.simple_query(&sql).await.unwrap(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+94
to
+97
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard table names before building Line 95 interpolates Suggested hardening pub async fn clear_table_with_client(client: &Client, table: &str) {
+ assert!(
+ table
+ .chars()
+ .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'),
+ "invalid fixture table name: {table}"
+ );
let sql = format!("TRUNCATE {}", table);
client.simple_query(&sql).await.unwrap();
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| pub async fn clear_table(table: &str) { | ||||||||||||||||||||||||||||||
| clear_table_with_client(&connect_with_tls(PROXY).await, table).await; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| pub async fn reset_schema() { | ||||||||||||||||||||||||||||||
| let port = std::env::var("CS_DATABASE__PORT") | ||||||||||||||||||||||||||||||
| .map(|s| s.parse().unwrap()) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| #[cfg(test)] | ||
| mod tests { | ||
| use crate::common::{ | ||
| clear_table, connect_with_tls, interleaved_indices, random_id, trace, PROXY, | ||
| }; | ||
|
|
||
| #[tokio::test] | ||
| async fn map_ope_order_text_asc() { | ||
| trace(); | ||
| let table = "encrypted_ope_order_text_asc"; | ||
| clear_table(table).await; | ||
| let client = connect_with_tls(PROXY).await; | ||
|
|
||
| let values = ["aardvark", "aplomb", "chimera", "chrysalis", "zephyr"]; | ||
|
|
||
| let insert = format!("INSERT INTO {table} (id, encrypted_text) VALUES ($1, $2)"); | ||
| for idx in interleaved_indices(values.len()) { | ||
| client | ||
| .query(&insert, &[&random_id(), &values[idx]]) | ||
| .await | ||
| .unwrap(); | ||
| } | ||
|
|
||
| let select = format!("SELECT encrypted_text FROM {table} ORDER BY encrypted_text"); | ||
| let rows = client.query(&select, &[]).await.unwrap(); | ||
|
|
||
| let actual: Vec<String> = rows.iter().map(|r| r.get(0)).collect(); | ||
| let expected: Vec<String> = values.iter().map(|s| s.to_string()).collect(); | ||
|
|
||
| assert_eq!(actual, expected); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn map_ope_order_text_desc() { | ||
| trace(); | ||
| let table = "encrypted_ope_order_text_desc"; | ||
| clear_table(table).await; | ||
| let client = connect_with_tls(PROXY).await; | ||
|
|
||
| let values = ["aardvark", "aplomb", "chimera", "chrysalis", "zephyr"]; | ||
|
|
||
| let insert = format!("INSERT INTO {table} (id, encrypted_text) VALUES ($1, $2)"); | ||
| for idx in interleaved_indices(values.len()) { | ||
| client | ||
| .query(&insert, &[&random_id(), &values[idx]]) | ||
| .await | ||
| .unwrap(); | ||
| } | ||
|
|
||
| let select = format!("SELECT encrypted_text FROM {table} ORDER BY encrypted_text DESC"); | ||
| let rows = client.query(&select, &[]).await.unwrap(); | ||
|
|
||
| let actual: Vec<String> = rows.iter().map(|r| r.get(0)).collect(); | ||
| let expected: Vec<String> = values.iter().rev().map(|s| s.to_string()).collect(); | ||
|
|
||
| assert_eq!(actual, expected); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn map_ope_order_int4_asc() { | ||
| trace(); | ||
| let table = "encrypted_ope_order_int4_asc"; | ||
| clear_table(table).await; | ||
| let client = connect_with_tls(PROXY).await; | ||
|
|
||
| let values: Vec<i32> = vec![-100, -1, 0, 1, 42, 1000, i32::MAX]; | ||
|
|
||
| let insert = format!("INSERT INTO {table} (id, encrypted_int4) VALUES ($1, $2)"); | ||
| for idx in interleaved_indices(values.len()) { | ||
| client | ||
| .query(&insert, &[&random_id(), &values[idx]]) | ||
| .await | ||
| .unwrap(); | ||
| } | ||
|
|
||
| let select = format!("SELECT encrypted_int4 FROM {table} ORDER BY encrypted_int4"); | ||
| let rows = client.query(&select, &[]).await.unwrap(); | ||
|
|
||
| let actual: Vec<i32> = rows.iter().map(|r| r.get(0)).collect(); | ||
| assert_eq!(actual, values); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn map_ope_order_int4_desc() { | ||
| trace(); | ||
| let table = "encrypted_ope_order_int4_desc"; | ||
| clear_table(table).await; | ||
| let client = connect_with_tls(PROXY).await; | ||
|
|
||
| let values: Vec<i32> = vec![-100, -1, 0, 1, 42, 1000, i32::MAX]; | ||
|
|
||
| let insert = format!("INSERT INTO {table} (id, encrypted_int4) VALUES ($1, $2)"); | ||
| for idx in interleaved_indices(values.len()) { | ||
| client | ||
| .query(&insert, &[&random_id(), &values[idx]]) | ||
| .await | ||
| .unwrap(); | ||
| } | ||
|
|
||
| let select = format!("SELECT encrypted_int4 FROM {table} ORDER BY encrypted_int4 DESC"); | ||
| let rows = client.query(&select, &[]).await.unwrap(); | ||
|
|
||
| let actual: Vec<i32> = rows.iter().map(|r| r.get(0)).collect(); | ||
| let expected: Vec<i32> = values.into_iter().rev().collect(); | ||
| assert_eq!(actual, expected); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn map_ope_order_nulls_last_by_default() { | ||
| trace(); | ||
| let table = "encrypted_ope_order_nulls_last"; | ||
| clear_table(table).await; | ||
| let client = connect_with_tls(PROXY).await; | ||
|
|
||
| let null_insert = format!("INSERT INTO {table} (id) VALUES ($1)"); | ||
| client.query(&null_insert, &[&random_id()]).await.unwrap(); | ||
|
|
||
| let insert = format!("INSERT INTO {table} (id, encrypted_text) VALUES ($1, $2), ($3, $4)"); | ||
| client | ||
| .query(&insert, &[&random_id(), &"a", &random_id(), &"b"]) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| let select = format!("SELECT encrypted_text FROM {table} ORDER BY encrypted_text"); | ||
| let rows = client.query(&select, &[]).await.unwrap(); | ||
|
|
||
| let actual: Vec<Option<String>> = rows.iter().map(|r| r.get(0)).collect(); | ||
| assert_eq!( | ||
| actual, | ||
| vec![Some("a".into()), Some("b".into()), None], | ||
| "NULLs should sort last by default" | ||
| ); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn map_ope_order_nulls_first() { | ||
| trace(); | ||
| let table = "encrypted_ope_order_nulls_first"; | ||
| clear_table(table).await; | ||
| let client = connect_with_tls(PROXY).await; | ||
|
|
||
| let insert = format!("INSERT INTO {table} (id, encrypted_text) VALUES ($1, $2), ($3, $4)"); | ||
| client | ||
| .query(&insert, &[&random_id(), &"a", &random_id(), &"b"]) | ||
| .await | ||
| .unwrap(); | ||
|
|
||
| let null_insert = format!("INSERT INTO {table} (id) VALUES ($1)"); | ||
| client.query(&null_insert, &[&random_id()]).await.unwrap(); | ||
|
|
||
| let select = | ||
| format!("SELECT encrypted_text FROM {table} ORDER BY encrypted_text NULLS FIRST"); | ||
| let rows = client.query(&select, &[]).await.unwrap(); | ||
|
|
||
| let actual: Vec<Option<String>> = rows.iter().map(|r| r.get(0)).collect(); | ||
| assert_eq!( | ||
| actual, | ||
| vec![None, Some("a".into()), Some("b".into())], | ||
| "NULLS FIRST should explicitly sort NULLs first" | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make the changelog entry release-facing.
The first sentence is changelog material; the SQL example and
IndexType::Openote read like implementation details. I'd move those details into docs or the PR description so[Unreleased]stays user-facing.As per coding guidelines, "Write changelog entries from the user's perspective, not implementation details."
🤖 Prompt for AI Agents