mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
test(buzz-db): detect ON CONFLICT( upserts at a token boundary
sql_update_sets() matched only the spaced ' on conflict ' spelling, so ON CONFLICT(request_id) DO UPDATE ... slipped past the approval append-only rule and the manifest content-UPDATE rule. Match on conflict and do update set at identifier boundaries and add no-space negative fixtures for both tables plus a no-space DO NOTHING accept fixture. Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -1947,19 +1947,25 @@ const SINGLE_TABLE_GUARD_TABLES: [&str; 5] = [
|
||||
];
|
||||
const DELETION_STORE_PATH: &str = "buzz-db/src/store/deletion.rs";
|
||||
|
||||
/// The text after each occurrence of `marker` that sits on identifier
|
||||
/// boundaries, so `on conflict` matches before a space or `(` alike.
|
||||
fn sql_token_suffixes<'a>(source: &'a str, marker: &str) -> Vec<&'a str> {
|
||||
let is_ident = |ch: char| ch.is_ascii_alphanumeric() || ch == '_';
|
||||
source
|
||||
.match_indices(marker)
|
||||
.filter_map(|(at, found)| {
|
||||
let rest = &source[at + found.len()..];
|
||||
(!source[..at].chars().next_back().is_some_and(is_ident)
|
||||
&& !rest.chars().next().is_some_and(is_ident))
|
||||
.then_some(rest)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn sql_table_suffixes<'a>(source: &'a str, verb: &str, table: &str) -> Vec<&'a str> {
|
||||
[format!("{verb} {table}"), format!("{verb} public.{table}")]
|
||||
.iter()
|
||||
.flat_map(|marker| {
|
||||
source.match_indices(marker).filter_map(|(at, found)| {
|
||||
let before = &source[..at];
|
||||
let rest = &source[at + found.len()..];
|
||||
let is_ident = |ch: char| ch.is_ascii_alphanumeric() || ch == '_';
|
||||
(!before.chars().next_back().is_some_and(is_ident)
|
||||
&& !rest.chars().next().is_some_and(is_ident))
|
||||
.then_some(rest)
|
||||
})
|
||||
})
|
||||
.flat_map(|marker| sql_token_suffixes(source, marker))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -1979,8 +1985,10 @@ fn sql_update_sets<'a>(source: &'a str, table: &str) -> Vec<(&'a str, &'a str)>
|
||||
.into_iter()
|
||||
.filter_map(|rest| {
|
||||
let statement = rest.split(['"', ';']).next()?;
|
||||
let (_, conflict) = statement.split_once(" on conflict ")?;
|
||||
conflict.split_once(" do update set ").map(|(_, set)| set)
|
||||
let conflict = *sql_token_suffixes(statement, "on conflict").first()?;
|
||||
sql_token_suffixes(conflict, "do update set")
|
||||
.first()
|
||||
.map(|set| set.trim_start())
|
||||
});
|
||||
updates
|
||||
.chain(conflict_updates)
|
||||
@@ -2510,6 +2518,7 @@ fn single_table_policy_rejects_approval_mutations() {
|
||||
"UPDATE community_deletion_approvals SET approved_by = $1 WHERE request_id = $2",
|
||||
"DELETE FROM community_deletion_approvals WHERE request_id = $1",
|
||||
"INSERT INTO community_deletion_approvals (request_id, note) VALUES ($1, $2) ON CONFLICT (request_id) DO UPDATE SET note = EXCLUDED.note",
|
||||
"INSERT INTO community_deletion_approvals (request_id, note) VALUES ($1, $2) ON CONFLICT(request_id) DO UPDATE SET note = EXCLUDED.note",
|
||||
] {
|
||||
let source = format!("async fn bypass() {{ query(\"{sql}\"); }}");
|
||||
assert_eq!(
|
||||
@@ -2522,6 +2531,7 @@ fn single_table_policy_rejects_approval_mutations() {
|
||||
let append = r#"async fn approve() {
|
||||
query("INSERT INTO community_deletion_approvals (request_id, note) VALUES ($1, $2)");
|
||||
query("INSERT INTO community_deletion_approvals (request_id, note) VALUES ($1, $2) ON CONFLICT (request_id) DO NOTHING");
|
||||
query("INSERT INTO community_deletion_approvals (request_id, note) VALUES ($1, $2) ON CONFLICT(request_id) DO NOTHING");
|
||||
}"#;
|
||||
assert!(
|
||||
single_table_guard_violations(DELETION_STORE_PATH, append, &mut Default::default())
|
||||
@@ -2580,6 +2590,7 @@ fn single_table_policy_rejects_manifest_content_updates() {
|
||||
for sql in [
|
||||
"UPDATE community_deletion_manifest_keys SET keys = $1 WHERE request_id = $2",
|
||||
"INSERT INTO community_deletion_manifest_keys (request_id, chunk_no, keys) VALUES ($1, $2, $3) ON CONFLICT (request_id, chunk_no) DO UPDATE SET keys = EXCLUDED.keys",
|
||||
"INSERT INTO community_deletion_manifest_keys (request_id, chunk_no, keys) VALUES ($1, $2, $3) ON CONFLICT(request_id, chunk_no) DO UPDATE SET keys = EXCLUDED.keys",
|
||||
] {
|
||||
let source = format!("async fn append() {{ guard_manifest_chunk_write(); query(\"{sql}\"); }}");
|
||||
let violations =
|
||||
|
||||
Reference in New Issue
Block a user