Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 12 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
[package]
name = "pg_stage_rs"
version = "0.1.3"
version = "0.2.0"
edition = "2021"
description = "PostgreSQL dump anonymizer - streaming obfuscation for plain and custom formats"

[features]
default = ["mimalloc-allocator", "parallel"]
mimalloc-allocator = ["dep:mimalloc"]
# Opt-in: requires cmake/cc in build env; swaps flate2 backend to zlib-ng for ~1.5-2x zlib throughput.
zlib-ng = ["flate2/zlib-ng"]
parallel = ["dep:crossbeam-channel", "dep:num_cpus"]

[dependencies]
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
flate2 = "1"
zstd = { version = "0.13", features = ["zstdmt"] }
memchr = "2"
crossbeam-channel = "0.5"
num_cpus = "1.16"
uuid = { version = "1", features = ["v4", "v5"] }
regex = "1"
rand = "0.8"
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
hmac = "0.12"
sha2 = "0.10"
thiserror = "2"
ahash = { version = "0.8", features = ["serde"] }
mimalloc = { version = "0.1", default-features = false, optional = true }
crossbeam-channel = { version = "0.5", optional = true }
num_cpus = { version = "1.16", optional = true }

[profile.release]
opt-level = 3
Expand Down
52 changes: 24 additions & 28 deletions src/conditions.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,42 @@
use regex::Regex;
use std::sync::Arc;

use crate::types::Condition;
use crate::types::{CompiledCondition, CondOp};
use crate::FastMap;

/// Check if conditions are met for a given row.
/// Returns true if at least one condition matches.
/// Returns true if conditions list is empty.
pub fn check_conditions<S: AsRef<str>>(
conditions: &[Condition],
values: &[S],
column_indices: &std::collections::HashMap<String, usize>,
/// Trait giving condition-evaluation access to a row's current (possibly already
/// mutated) values by column index.
pub trait RowRead {
fn len(&self) -> usize;
fn value_at(&self, idx: usize) -> &str;
}

/// Check if a compiled condition list matches the current row.
/// Returns true if the list is empty, or if at least one condition matches.
pub fn check_conditions(
conditions: &[CompiledCondition],
row: &dyn RowRead,
column_indices: &FastMap<Arc<str>, usize>,
) -> bool {
if conditions.is_empty() {
return true;
}

for condition in conditions {
let col_idx = match column_indices.get(&condition.column_name) {
Some(idx) => *idx,
let col_idx = match column_indices.get(condition.column_name.as_ref()) {
Some(&idx) => idx,
None => continue,
};
if col_idx >= values.len() {
if col_idx >= row.len() {
continue;
}
let col_value = values[col_idx].as_ref();

let matched = match condition.operation.as_str() {
"equal" => col_value == condition.value,
"not_equal" => col_value != condition.value,
"by_pattern" => {
if let Ok(re) = Regex::new(&condition.value) {
re.is_match(col_value)
} else {
false
}
}
_ => false,
let col_value = row.value_at(col_idx);
let matched = match &condition.op {
CondOp::Equal(v) => col_value == v.as_str(),
CondOp::NotEqual(v) => col_value != v.as_str(),
CondOp::ByPattern(re) => re.is_match(col_value),
};

if matched {
return true;
}
}

false
}
Loading
Loading