1use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8#[non_exhaustive]
9pub enum DbmsKind {
10 #[default]
11 Generic,
12 Mssql,
13 Mysql,
14 Postgresql,
15 Oracle,
16}
17
18pub struct UnknownDBMSError;
20
21impl TryFrom<&str> for DbmsKind {
22 type Error = UnknownDBMSError;
23
24 fn try_from(value: &str) -> Result<Self, Self::Error> {
25 let res = match value.to_lowercase().as_str() {
26 "" => Self::Generic,
27 "mssql" => Self::Mssql,
28 "mysql" => Self::Mysql,
29 "postgresql" => Self::Postgresql,
30 "oracle" => Self::Oracle,
31 _ => return Err(UnknownDBMSError),
32 };
33 Ok(res)
34 }
35}
36
37#[allow(deprecated)]
38#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]
39#[serde(rename_all = "snake_case")]
40#[non_exhaustive]
41pub enum SqlObfuscationMode {
42 #[default]
43 #[deprecated = "kept for compatibility with agent's obfuscator but has unintuitive behavior"]
44 #[serde(alias = "")]
45 Unspecified,
46 NormalizeOnly,
47 ObfuscateOnly,
48 ObfuscateAndNormalize,
49}
50
51#[derive(Debug, Default, Clone, Deserialize)]
53pub struct SqlObfuscateConfig {
54 pub replace_digits: bool,
55 pub keep_sql_alias: bool,
56 pub dollar_quoted_func: bool,
57 pub keep_null: bool,
58 pub keep_boolean: bool,
59 pub keep_positional_parameter: bool,
60 pub keep_trailing_semicolon: bool,
61 pub keep_identifier_quotation: bool,
62 pub replace_bind_parameter: bool,
63 pub remove_space_between_parentheses: bool,
64 pub keep_json_path: bool,
65 pub obfuscation_mode: SqlObfuscationMode,
66}
67
68fn is_whitespace(b: u8) -> bool {
69 matches!(b, b' ' | b'\t' | b'\n' | b'\r' | 0x0B | 0x0C)
70}
71
72fn is_ident_start(b: u8) -> bool {
73 b.is_ascii_alphabetic() || b == b'_' || b > 127
74}
75
76fn is_ident_char(b: u8) -> bool {
77 b.is_ascii_alphanumeric()
81 || b == b'_'
82 || b == b'$'
83 || b == b'#'
84 || b == b'*'
85 || b == b'@'
86 || b > 127
87}
88
89fn apply_replace_digits(ident: &str) -> String {
94 let bytes = ident.as_bytes();
95 let mut result = String::with_capacity(ident.len());
96 let mut i = 0;
97 while i < bytes.len() {
98 if bytes[i].is_ascii_digit() {
99 while i < bytes.len() && bytes[i].is_ascii_digit() {
100 i += 1;
101 }
102 result.push('?');
103 } else {
104 let c = ident[i..].chars().next().unwrap_or(' ');
106 result.push(c);
107 i += c.len_utf8();
108 }
109 }
110 result
111}
112
113fn find_quoted_string_end(bytes: &[u8], start: usize) -> Option<usize> {
115 if bytes.get(start) != Some(&b'\'') {
116 return None;
117 }
118 let short_end = {
120 let mut i = start + 1;
121 let mut result = None;
122 while i < bytes.len() {
123 if bytes[i] == b'\'' {
124 if i + 1 < bytes.len() && bytes[i + 1] == b'\'' {
125 i += 2; continue;
127 } else {
128 result = Some(i + 1);
129 break;
130 }
131 }
132 i += 1;
133 }
134 result
135 };
136
137 let short_at_boundary = short_end.is_some_and(|end| {
141 !bytes
142 .get(end)
143 .is_some_and(|&c| c.is_ascii_alphanumeric() || c == b'_')
144 });
145
146 if short_at_boundary {
147 return short_end;
148 }
149
150 let mut i = start + 1;
152 let mut escaped = false;
153 while i < bytes.len() {
154 if escaped {
155 escaped = false;
156 } else if bytes[i] == b'\\' {
157 escaped = true;
158 } else if bytes[i] == b'\'' {
159 if i + 1 < bytes.len() && bytes[i + 1] == b'\'' {
160 i += 1; } else {
162 return Some(i + 1);
163 }
164 }
165 i += 1;
166 }
167
168 short_end
170}
171
172fn find_dollar_quote_end(bytes: &[u8], start: usize) -> Option<(usize, usize, usize)> {
175 let n = bytes.len();
176 if start >= n || bytes[start] != b'$' {
177 return None;
178 }
179 let mut tag_end = start + 1;
181 while tag_end < n && bytes[tag_end] != b'$' {
182 if bytes[tag_end] == b'\n' {
183 return None; }
185 tag_end += 1;
186 }
187 if tag_end >= n {
188 return None;
189 }
190 let tag = &bytes[start..=tag_end];
192 let inner_start = tag_end + 1;
193
194 let mut i = inner_start;
196 while i + tag.len() <= n {
197 if bytes[i] == b'$' && bytes[i..].starts_with(tag) {
198 return Some((inner_start, i, i + tag.len()));
199 }
200 i += 1;
201 }
202 None
203}
204
205struct Tokenizer<'a> {
206 s: &'a str,
207 bytes: &'a [u8],
208 pos: usize,
209 result: String,
210 dbms: DbmsKind,
211 config: &'a SqlObfuscateConfig,
212 before_as_len: Option<usize>,
214 pending_savepoint: bool,
216 last_was_placeholder: bool,
219 pending_json_path: bool,
221 last_was_assign: bool,
224}
225
226impl<'a> Tokenizer<'a> {
227 fn new(s: &'a str, config: &'a SqlObfuscateConfig, dbms: DbmsKind) -> Self {
228 Self {
229 s,
230 bytes: s.as_bytes(),
231 pos: 0,
232 result: String::with_capacity(s.len()),
233 dbms,
234 config,
235 before_as_len: None,
236 pending_savepoint: false,
237 last_was_placeholder: false,
238 pending_json_path: false,
239 last_was_assign: false,
240 }
241 }
242
243 fn peek(&self, offset: usize) -> Option<u8> {
244 self.bytes.get(self.pos + offset).copied()
245 }
246
247 fn at_end(&self) -> bool {
248 self.pos >= self.bytes.len()
249 }
250
251 fn is_normalize_only(&self) -> bool {
252 matches!(
253 self.config.obfuscation_mode,
254 SqlObfuscationMode::NormalizeOnly
255 )
256 }
257
258 fn is_obfuscate_only(&self) -> bool {
259 matches!(
260 self.config.obfuscation_mode,
261 SqlObfuscationMode::ObfuscateOnly
262 )
263 }
264
265 #[allow(deprecated)]
266 fn is_unspecified_obfuscate_mode(&self) -> bool {
267 matches!(
268 self.config.obfuscation_mode,
269 SqlObfuscationMode::Unspecified
270 )
271 }
272
273 fn last_char(&self) -> Option<u8> {
274 self.result.as_bytes().last().copied()
275 }
276
277 fn last_nonspace_char(&self) -> Option<u8> {
278 self.result
279 .as_bytes()
280 .iter()
281 .rev()
282 .find(|&&b| b != b' ')
283 .copied()
284 }
285
286 fn space(&mut self) {
291 if !self.result.is_empty()
292 && self.last_char() != Some(b' ')
293 && !(self.last_char() == Some(b'.') && {
294 let len = self.result.len();
298 if len < 2 {
299 false
300 } else {
301 let before_dot = self.result.as_bytes()[len - 2];
302 before_dot.is_ascii_alphanumeric()
303 || before_dot == b'_'
304 || before_dot == b'"'
305 || before_dot == b']'
306 || before_dot == b'#'
307 || before_dot == b'?'
308 || before_dot > 127 }
310 })
311 && !(self.last_char() == Some(b'(')
312 && (self.config.remove_space_between_parentheses || self.is_obfuscate_only()))
313 {
314 if self.last_nonspace_char() != Some(b'?') {
318 self.last_was_placeholder = false;
319 }
320 self.result.push(' ');
321 } else if self.last_char() == Some(b' ')
322 && !matches!(self.last_nonspace_char(), Some(b'?') | Some(b'('))
323 {
324 self.last_was_placeholder = false;
328 }
329 }
330
331 fn emit(&mut self, token: &str) {
333 self.space();
334 self.result.push_str(token);
335 self.last_was_placeholder = false;
336 self.last_was_assign = false;
337 }
338
339 fn emit_char(&mut self, c: char) {
341 self.space();
342 self.result.push(c);
343 self.last_was_placeholder = c == '?';
344 self.last_was_assign = false;
345 }
346
347 fn emit_placeholder(&mut self) {
351 if self.is_unspecified_obfuscate_mode() && self.last_was_placeholder {
352 return;
354 }
355 self.emit_char('?');
356 }
357
358 fn skip_whitespace(&mut self) {
359 while !self.at_end() && is_whitespace(self.bytes[self.pos]) {
360 self.pos += 1;
361 }
362 while !self.at_end() && self.bytes[self.pos] > 127 {
364 if let Some(c) = self.s[self.pos..].chars().next() {
365 if c.is_whitespace() {
366 self.pos += c.len_utf8();
367 while !self.at_end() && is_whitespace(self.bytes[self.pos]) {
369 self.pos += 1;
370 }
371 continue;
372 }
373 }
374 break;
375 }
376 }
377
378 fn skip_line_comment(&mut self) {
379 while !self.at_end() && self.bytes[self.pos] != b'\n' {
380 self.pos += 1;
381 }
382 }
383
384 fn skip_block_comment(&mut self) {
385 while self.pos + 1 < self.bytes.len() {
387 if self.bytes[self.pos] == b'*' && self.bytes[self.pos + 1] == b'/' {
388 self.pos += 2;
389 return;
390 }
391 self.pos += 1;
392 }
393 self.pos = self.bytes.len();
395 }
396
397 fn handle_single_quote(&mut self) {
399 let str_start = self.pos;
400 if let Some(end) = find_quoted_string_end(self.bytes, self.pos) {
401 self.pos = end;
402 } else {
403 self.pos = self.bytes.len();
405 }
406 if self.pending_json_path || self.is_normalize_only() {
407 self.pending_json_path = false;
408 let raw = &self.s[str_start..self.pos].to_string();
410 if !self.maybe_consume_alias_next() {
411 self.emit(raw);
412 }
413 return;
414 }
415 if !self.maybe_consume_alias_next() {
416 self.emit_placeholder();
417 }
418 }
419
420 fn maybe_consume_alias_next(&mut self) -> bool {
424 if let Some(before_len) = self.before_as_len.take() {
425 self.result.truncate(before_len);
427 return true; }
429 false
430 }
431
432 fn emit_identifier(&mut self, ident: &str) {
434 let lower = ident.to_ascii_lowercase();
435
436 if self.pending_savepoint {
438 self.pending_savepoint = false;
439 if self.maybe_consume_alias_next() {
440 return;
441 }
442 self.emit_placeholder();
443 return;
444 }
445
446 if !self.config.keep_null && !self.is_normalize_only() && lower == "null" {
448 if self.maybe_consume_alias_next() {
449 return;
450 }
451 self.emit_placeholder();
452 return;
453 }
454
455 if !self.config.keep_boolean
457 && !self.is_normalize_only()
458 && (lower == "true" || lower == "false")
459 {
460 if self.maybe_consume_alias_next() {
461 return;
462 }
463 self.emit_placeholder();
464 return;
465 }
466
467 if !self.config.keep_sql_alias
471 && !self.is_normalize_only()
472 && !self.is_obfuscate_only()
473 && lower == "as"
474 {
475 if self.last_char() == Some(b' ') {
478 self.result.pop();
479 }
480 let before_len = self.result.len();
481 self.space();
482 self.result.push_str(ident);
483 self.before_as_len = Some(before_len);
484 self.last_was_placeholder = false;
487 return;
488 }
489
490 if self.before_as_len.is_some()
498 && matches!(
499 lower.as_str(),
500 "begin"
501 | "end"
502 | "select"
503 | "insert"
504 | "update"
505 | "delete"
506 | "from"
507 | "where"
508 | "join"
509 | "on"
510 | "set"
511 | "values"
512 | "into"
513 | "group"
514 | "order"
515 | "having"
516 | "union"
517 | "intersect"
518 | "except"
519 | "limit"
520 | "offset"
521 | "with"
522 | "create"
523 | "drop"
524 | "alter"
525 | "truncate"
526 )
527 {
528 self.before_as_len = None;
529 }
530
531 if self.maybe_consume_alias_next() {
532 return;
533 }
534
535 if lower == "savepoint" {
537 self.pending_savepoint = true;
538 }
539
540 let out = if self.config.replace_digits {
541 apply_replace_digits(ident)
542 } else {
543 ident.to_string()
544 };
545 self.emit(&out);
546 }
547
548 fn handle_dot_after_quoted_ident(&mut self) {
551 if !self.at_end() && self.bytes[self.pos] == b'.' {
552 let next = self.bytes.get(self.pos + 1).copied();
553 if self.is_obfuscate_only() {
555 self.result.push('.');
556 self.pos += 1;
557 return;
558 }
559 match next {
560 Some(b'`') | Some(b'"') | Some(b'[') => {
561 self.result.push_str(" . ");
562 self.pos += 1; }
564 Some(b'*') => {
565 self.result.push_str(".*");
566 self.pos += 2;
567 }
568 Some(c) if is_ident_start(c) => {
569 self.result.push_str(" . ");
570 self.pos += 1; }
572 _ => {
573 self.result.push('.');
574 self.pos += 1;
575 }
576 }
577 }
578 }
579
580 fn handle_dot_after_bracket_ident(&mut self) {
582 if !self.at_end() && self.bytes[self.pos] == b'.' {
583 let next = self.bytes.get(self.pos + 1).copied();
584 if next == Some(b'[') {
585 self.result.push_str(" . ");
586 self.pos += 1; } else {
588 self.result.push('.');
589 self.pos += 1;
590 }
591 }
592 }
593
594 fn consume_number(&mut self) {
596 self.consume_number_inner(false);
597 }
598
599 fn consume_number_inner(&mut self, seen_dot: bool) {
600 let mut saw_dot = seen_dot;
605 let mut saw_exp = false;
606 while !self.at_end() {
607 let b = self.bytes[self.pos];
608 match b {
609 b'0'..=b'9' => {
610 self.pos += 1;
611 }
612 b'.' if !saw_dot => {
613 saw_dot = true;
614 self.pos += 1;
615 }
616 b'e' | b'E' if !saw_exp => {
617 saw_exp = true;
618 self.pos += 1;
619 if !self.at_end() && matches!(self.bytes[self.pos], b'+' | b'-') {
621 self.pos += 1;
622 }
623 }
624 _ => break,
628 }
629 }
630 }
631
632 fn process(&mut self) {
633 while !self.at_end() {
634 let b = self.bytes[self.pos];
635
636 match b {
637 b if is_whitespace(b) => {
639 self.pos += 1;
640 self.skip_whitespace();
641 if self.before_as_len.is_none() {
643 self.space();
644 }
645 }
646
647 b'-' if self.peek(1) == Some(b'-') => {
649 self.pos += 2;
650 self.skip_line_comment();
651 }
652 b'/' if self.peek(1) == Some(b'/') => {
653 self.pos += 2;
654 self.skip_line_comment();
655 }
656
657 b'#' => {
660 let next = self.peek(1);
661 let is_sqlserver = matches!(self.dbms, DbmsKind::Mssql);
662 match next {
663 Some(b)
664 if is_sqlserver
665 && (b.is_ascii_alphanumeric() || b == b'_' || b == b'#') =>
666 {
667 let start = self.pos;
669 while !self.at_end() && is_ident_char(self.bytes[self.pos]) {
670 self.pos += 1;
671 }
672 let ident = &self.s[start..self.pos];
673 if self.maybe_consume_alias_next() {
674 continue;
675 }
676 let out = if self.config.replace_digits {
677 apply_replace_digits(ident)
678 } else {
679 ident.to_string()
680 };
681 self.emit(&out);
682 }
683 Some(b'>') if matches!(self.dbms, DbmsKind::Postgresql) => {
685 if self.maybe_consume_alias_next() {
686 continue;
687 }
688 if self.peek(2) == Some(b'>') {
689 self.emit("#>>");
690 self.pos += 3;
691 } else {
692 self.emit("#>");
693 self.pos += 2;
694 }
695 self.space();
696 }
697 Some(b'-') if matches!(self.dbms, DbmsKind::Postgresql) => {
698 if self.maybe_consume_alias_next() {
699 continue;
700 }
701 self.emit("#-");
702 self.pos += 2;
703 self.space();
704 }
705 _ => {
706 self.pos += 1;
708 self.skip_line_comment();
709 }
710 }
711 }
712
713 b'/' if self.peek(1) == Some(b'*') => {
715 self.pos += 2;
716 self.skip_block_comment();
717 }
718
719 b';' => {
721 self.pos += 1;
722 if self.is_obfuscate_only()
726 || (!self.is_unspecified_obfuscate_mode()
727 && self.config.keep_trailing_semicolon)
728 {
729 if self.maybe_consume_alias_next() {
730 continue;
731 }
732 self.result.push(';');
733 } else if self.is_unspecified_obfuscate_mode() {
734 self.last_was_placeholder = true;
736 }
737 }
738
739 b'(' => {
741 if self.before_as_len.is_some() && !self.is_unspecified_obfuscate_mode() {
742 self.before_as_len = None;
744 } else if self.before_as_len.is_some() {
745 self.maybe_consume_alias_next();
748 self.pos += 1;
749 self.skip_whitespace();
750 continue; }
752 self.pending_savepoint = false;
753 self.space();
754 self.result.push('(');
755 self.pos += 1;
756 let add_space = if self.is_unspecified_obfuscate_mode() {
759 !self.is_obfuscate_only()
760 } else {
761 !self.config.remove_space_between_parentheses && !self.is_obfuscate_only()
762 };
763 if add_space {
764 self.skip_whitespace();
765 self.result.push(' ');
766 }
767 }
768
769 b')' => {
771 if self.maybe_consume_alias_next() {
772 continue;
773 }
774 let add_close_space = if self.is_unspecified_obfuscate_mode() {
776 !self.is_obfuscate_only()
777 } else {
778 !self.config.remove_space_between_parentheses && !self.is_obfuscate_only()
779 };
780 if add_close_space {
781 if !matches!(self.last_char(), Some(b'(') | Some(b' ') | None) {
783 self.result.push(' ');
784 }
785 }
786 self.result.push(')');
787 self.pos += 1;
788 }
792
793 b',' => {
795 if self.maybe_consume_alias_next() {
796 continue;
797 }
798 self.pos += 1;
799 if self.last_was_placeholder && self.is_unspecified_obfuscate_mode() {
804 while self.last_char() == Some(b' ') {
806 self.result.pop();
807 }
808 continue;
809 }
810 if self.last_char() == Some(b' ') {
812 self.result.pop();
813 }
814 self.result.push(',');
815 }
817
818 b'\'' => {
820 self.handle_single_quote();
821 }
822
823 b'`' => {
825 self.pos += 1;
826 let mut ident_buf = String::new();
827 loop {
828 if self.at_end() {
829 break;
830 }
831 if self.bytes[self.pos] == b'`' {
832 if self.bytes.get(self.pos + 1) == Some(&b'`') {
833 ident_buf.push('`');
835 self.pos += 2;
836 } else {
837 self.pos += 1; break;
839 }
840 } else {
841 let c = self.s[self.pos..].chars().next().unwrap_or(' ');
842 ident_buf.push(c);
843 self.pos += c.len_utf8();
844 }
845 }
846 let out = if ident_buf.chars().all(char::is_whitespace) {
849 format!("`{ident_buf}`")
850 } else if self.config.replace_digits {
851 apply_replace_digits(&ident_buf)
852 } else {
853 ident_buf.clone()
854 };
855 if self.maybe_consume_alias_next() {
856 } else {
858 self.emit(&out);
859 self.handle_dot_after_quoted_ident();
860 }
861 }
862
863 b'"' => {
865 self.pos += 1;
866 let mut ident_buf = String::new();
868 while !self.at_end() {
869 if self.bytes[self.pos] == b'"' {
870 if self.bytes.get(self.pos + 1) == Some(&b'"') {
871 ident_buf.push('"'); self.pos += 2;
873 } else {
874 break;
875 }
876 } else {
877 let ch = self.s[self.pos..].chars().next().unwrap_or('\0');
878 ident_buf.push(ch);
879 self.pos += ch.len_utf8();
880 }
881 }
882 let ident_owned = ident_buf;
883 let ident = ident_owned.as_str();
884 if !self.at_end() {
885 self.pos += 1; }
887 let is_string_value = self.last_was_assign;
890 if self.pending_savepoint
892 || (!ident.is_empty() && ident.chars().all(|c| c.is_whitespace()))
893 || (!self.is_normalize_only() && is_string_value)
894 {
895 self.pending_savepoint = false;
896 if self.maybe_consume_alias_next() {
897 } else {
899 self.emit_placeholder();
900 self.handle_dot_after_quoted_ident();
901 }
902 } else if (self.config.keep_identifier_quotation
903 && !self.is_unspecified_obfuscate_mode())
904 || self.is_obfuscate_only()
905 {
906 let quoted = format!("\"{ident}\"");
910 if self.maybe_consume_alias_next() {
911 } else {
913 self.emit("ed);
914 self.handle_dot_after_quoted_ident();
915 }
916 } else {
917 let out = if ident.is_empty() {
922 format!("\"{ident}\"")
923 } else {
924 ident.to_string()
925 };
926 if self.maybe_consume_alias_next() {
927 } else {
929 self.emit(&out);
930 self.handle_dot_after_quoted_ident();
931 }
932 }
933 }
934
935 b'[' => {
937 if matches!(self.dbms, DbmsKind::Mssql) {
938 self.pos += 1;
939 let id_start = self.pos;
940 while !self.at_end() && self.bytes[self.pos] != b']' {
941 self.pos += 1;
942 }
943 let ident = &self.s[id_start..self.pos];
944 if !self.at_end() {
945 self.pos += 1; }
947 if self.maybe_consume_alias_next() {
948 } else {
950 let out = if self.config.replace_digits {
951 apply_replace_digits(ident)
952 } else {
953 ident.to_string()
954 };
955 self.emit(&out);
956 self.handle_dot_after_bracket_ident();
957 }
958 } else {
959 if self.before_as_len.is_some() {
962 self.pos += 1; while !self.at_end() && self.bytes[self.pos] != b']' {
964 self.pos += 1;
965 }
966 if !self.at_end() {
967 self.pos += 1; }
969 self.maybe_consume_alias_next();
970 } else {
971 self.space();
972 self.result.push('[');
973 self.pos += 1;
974 self.skip_whitespace();
975 self.space();
976 }
977 }
978 }
979
980 b']' => {
981 if self.maybe_consume_alias_next() {
982 continue;
983 }
984 if !matches!(self.last_char(), Some(b'[') | Some(b' ') | None) {
985 self.space();
986 }
987 self.result.push(']');
988 self.pos += 1;
989 if !self.at_end() && self.bytes[self.pos] == b'.' {
991 self.result.push_str(" . ");
992 self.pos += 1; }
994 }
995
996 b'$' => {
998 let next = self.peek(1);
999 match next {
1000 Some(b) if b.is_ascii_digit() || b == b'?' => {
1002 let token_start = self.pos;
1003 self.pos += 1; while !self.at_end()
1008 && (self.bytes[self.pos].is_ascii_digit()
1009 || self.bytes[self.pos] == b'?')
1010 {
1011 self.pos += 1;
1012 }
1013 if !self.at_end() && self.bytes[self.pos] == b'.' {
1016 self.pos += 1; while !self.at_end() && self.bytes[self.pos].is_ascii_digit() {
1018 self.pos += 1;
1019 }
1020 }
1021 if self.maybe_consume_alias_next() {
1022 continue;
1023 }
1024 let keep = (self.config.keep_positional_parameter
1029 && !self.is_unspecified_obfuscate_mode())
1030 || self.is_normalize_only()
1031 || self.is_obfuscate_only();
1032 if keep {
1033 self.emit(&self.s[token_start..self.pos]);
1034 } else {
1035 self.emit_placeholder();
1036 }
1037 }
1038 _ if next == Some(b'$')
1040 || next.is_some_and(|c| c.is_ascii_alphabetic() || c == b'_') =>
1041 {
1042 let start = self.pos;
1043 if let Some((inner_start, inner_end, outer_end)) =
1044 find_dollar_quote_end(self.bytes, start)
1045 {
1046 if self.maybe_consume_alias_next() {
1047 self.pos = outer_end;
1048 continue;
1049 }
1050 if self.is_normalize_only() {
1051 let tag_str = &self.s[start..inner_start];
1053 let inner = &self.s[inner_start..inner_end];
1054 let close_tag = &self.s[inner_end..outer_end];
1055 let normalized_inner =
1056 obfuscate_sql(inner, self.config, self.dbms);
1057 self.space();
1058 self.result.push_str(tag_str);
1059 self.result.push_str(&normalized_inner);
1060 self.result.push_str(close_tag);
1061 } else if self.config.dollar_quoted_func {
1062 let tag_str = &self.s[start..inner_start];
1064 let inner = &self.s[inner_start..inner_end];
1065 let close_tag = &self.s[inner_end..outer_end];
1066 let obfuscated_inner =
1067 obfuscate_sql(inner, self.config, self.dbms);
1068 if obfuscated_inner.trim() == "?" {
1071 self.emit_placeholder();
1072 } else {
1073 self.space();
1074 self.result.push_str(tag_str);
1075 self.result.push_str(&obfuscated_inner);
1076 self.result.push_str(close_tag);
1077 }
1078 } else {
1079 self.emit_placeholder();
1081 }
1082 self.pos = outer_end;
1083 } else {
1084 self.pos += 1; let id_start_pos = self.pos;
1088 while !self.at_end()
1089 && (is_ident_char(self.bytes[self.pos])
1090 || self.bytes[self.pos] == b'$')
1091 {
1092 self.pos += 1;
1093 }
1094 let ident = &self.s[id_start_pos - 1..self.pos]; if self.maybe_consume_alias_next() {
1096 continue;
1097 }
1098 self.emit(ident);
1099 }
1100 let _ = b; }
1102 _ => {
1103 let start = self.pos;
1105 self.pos += 1; while !self.at_end()
1107 && (is_ident_char(self.bytes[self.pos])
1108 || self.bytes[self.pos] == b'$')
1109 {
1110 self.pos += 1;
1111 }
1112 let token = &self.s[start..self.pos];
1113 if self.maybe_consume_alias_next() {
1114 continue;
1115 }
1116 self.emit(token);
1117 }
1118 }
1119 }
1120
1121 b'0' if matches!(self.peek(1), Some(b'x') | Some(b'X')) => {
1123 self.pos += 2; while !self.at_end() && self.bytes[self.pos].is_ascii_hexdigit() {
1125 self.pos += 1;
1126 }
1127 if self.maybe_consume_alias_next() {
1128 continue;
1129 }
1130 self.emit_placeholder();
1131 }
1132
1133 b'X' | b'x' if self.peek(1) == Some(b'\'') => {
1135 self.pos += 1; if let Some(end) = find_quoted_string_end(self.bytes, self.pos) {
1137 self.pos = end;
1138 } else {
1139 self.pos += 1;
1140 }
1141 if self.maybe_consume_alias_next() {
1142 continue;
1143 }
1144 self.emit_placeholder();
1145 }
1146
1147 b'%' => {
1149 let next = self.peek(1);
1150 match next {
1151 Some(b)
1152 if b.is_ascii_alphabetic() || b == b'_' || b == b'@' || b == b'#' =>
1153 {
1154 self.pos += 2;
1157 if self.maybe_consume_alias_next() {
1158 continue;
1159 }
1160 self.emit_placeholder();
1161 }
1162 Some(b'(') => {
1163 self.pos += 2; while !self.at_end() && self.bytes[self.pos] != b')' {
1165 self.pos += 1;
1166 }
1167 if !self.at_end() {
1168 self.pos += 1;
1169 } if !self.at_end() && self.bytes[self.pos].is_ascii_alphabetic() {
1172 self.pos += 1;
1173 }
1174 if self.maybe_consume_alias_next() {
1175 continue;
1176 }
1177 self.emit_placeholder();
1178 }
1179 Some(b) if b > 127 => {
1180 let next_char = self.s[self.pos + 1..].chars().next();
1184 if let Some(nc) = next_char.filter(|c| c.is_alphabetic() || *c == '_') {
1185 let skip = 1 + nc.len_utf8();
1186 self.pos += skip;
1187 if self.maybe_consume_alias_next() {
1188 continue;
1189 }
1190 self.emit_placeholder();
1191 } else {
1192 if self.maybe_consume_alias_next() {
1193 continue;
1194 }
1195 self.space();
1196 self.result.push('%');
1197 self.pos += 1;
1198 self.space();
1199 }
1200 }
1201 _ => {
1202 if self.maybe_consume_alias_next() {
1204 continue;
1205 }
1206 self.space();
1207 self.result.push('%');
1208 self.pos += 1;
1209 self.space();
1210 }
1211 }
1212 }
1213
1214 b'.' if self.peek(1).is_some_and(|b| b.is_ascii_digit())
1219 && !self
1220 .last_char()
1221 .is_some_and(|b| is_ident_char(b) || b == b'"' || b == b'`') =>
1222 {
1223 let num_start = self.pos;
1224 self.pos += 1; self.consume_number_inner(true);
1228 if self.maybe_consume_alias_next() {
1229 continue;
1230 }
1231 if self.is_normalize_only() {
1232 let raw = self.s[num_start..self.pos].to_string();
1233 self.emit(&raw);
1234 } else {
1235 self.emit_placeholder();
1236 }
1237 }
1238
1239 b'.' => {
1244 let after_dot_is_digit = self.peek(1).is_some_and(|b| b.is_ascii_digit());
1245 let preceded_by_ident = self
1246 .last_char()
1247 .is_some_and(|b| is_ident_char(b) || b == b'"' || b == b'`');
1248 if after_dot_is_digit && preceded_by_ident {
1249 } else if !after_dot_is_digit && !preceded_by_ident {
1251 if self.maybe_consume_alias_next() {
1254 continue;
1255 }
1256 self.space();
1257 self.result.push('.');
1258 self.pos += 1;
1259 continue;
1260 }
1261 if after_dot_is_digit && preceded_by_ident {
1262 let start = self.pos; self.pos += 1; while !self.at_end()
1266 && (is_ident_char(self.bytes[self.pos]) || self.bytes[self.pos] == b'.')
1267 {
1268 if self.bytes[self.pos] == b'.' {
1269 if self
1270 .peek(1)
1271 .is_some_and(|b| b.is_ascii_digit() || is_ident_char(b))
1272 {
1273 self.pos += 1;
1274 } else {
1275 break;
1276 }
1277 } else {
1278 self.pos += 1;
1279 }
1280 }
1281 let suffix = &self.s[start..self.pos];
1282 let out = if self.config.replace_digits {
1284 apply_replace_digits(suffix)
1285 } else {
1286 suffix.to_string()
1287 };
1288 self.result.push_str(&out);
1290 } else {
1291 self.result.push('.');
1292 self.pos += 1;
1293 }
1294 }
1295
1296 b'0'..=b'9' => {
1298 let num_start = self.pos;
1299 self.consume_number();
1300 if self.maybe_consume_alias_next() {
1301 continue;
1302 }
1303 if self.pending_json_path || self.is_normalize_only() {
1304 self.pending_json_path = false;
1305 let raw = self.s[num_start..self.pos].to_string();
1306 self.emit(&raw);
1307 } else {
1308 self.emit_placeholder();
1309 }
1310 }
1311
1312 b'{' => {
1314 if self.maybe_consume_alias_next() {
1315 continue;
1316 }
1317 let mut peek_pos = self.pos + 1;
1319 while peek_pos < self.bytes.len() && is_whitespace(self.bytes[peek_pos]) {
1320 peek_pos += 1;
1321 }
1322 let is_call = peek_pos + 4 <= self.bytes.len()
1324 && self.bytes[peek_pos..peek_pos + 4].eq_ignore_ascii_case(b"call")
1325 && (peek_pos + 4 >= self.bytes.len()
1326 || !self.bytes[peek_pos + 4].is_ascii_alphanumeric());
1327 if is_call {
1328 self.space();
1329 self.result.push('{');
1330 self.pos += 1;
1331 self.skip_whitespace();
1332 self.result.push(' ');
1333 } else {
1334 let mut depth = 1usize;
1336 self.pos += 1; while !self.at_end() && depth > 0 {
1338 match self.bytes[self.pos] {
1339 b'{' => {
1340 depth += 1;
1341 self.pos += 1;
1342 }
1343 b'}' => {
1344 depth -= 1;
1345 self.pos += 1;
1346 }
1347 b'\'' => {
1348 if let Some(end) = find_quoted_string_end(self.bytes, self.pos)
1349 {
1350 self.pos = end;
1351 } else {
1352 self.pos += 1;
1353 }
1354 }
1355 _ => {
1356 self.pos += 1;
1357 }
1358 }
1359 }
1360 self.emit_placeholder();
1361 }
1362 }
1363 b'}' => {
1364 if self.maybe_consume_alias_next() {
1365 continue;
1366 }
1367 self.space();
1369 self.result.push('}');
1370 self.pos += 1;
1371 }
1372
1373 b'@' => {
1375 if self.peek(1) == Some(b'@') {
1376 let start = self.pos;
1378 self.pos += 2; while !self.at_end() && is_ident_char(self.bytes[self.pos]) {
1380 self.pos += 1;
1381 }
1382 let token = &self.s[start..self.pos];
1383 if self.maybe_consume_alias_next() {
1384 continue;
1385 }
1386 self.emit(token);
1387 } else if self.peek(1).is_some_and(|b| {
1388 b.is_ascii_alphanumeric()
1389 || b == b'_'
1390 || b == b'#'
1391 || b == b'$'
1392 || b == b'*'
1393 }) {
1394 let start = self.pos;
1396 self.pos += 1; while !self.at_end() && is_ident_char(self.bytes[self.pos]) {
1398 self.pos += 1;
1399 }
1400 let token = self.s[start..self.pos].to_string();
1401 if self.maybe_consume_alias_next() {
1402 continue;
1403 }
1404 if self.config.replace_digits && token.chars().any(|c| c.is_ascii_digit()) {
1407 let replaced = apply_replace_digits(&token);
1408 self.emit(&replaced);
1409 } else if self.config.replace_bind_parameter
1410 && !self.is_unspecified_obfuscate_mode()
1411 {
1412 self.emit_placeholder();
1413 } else {
1414 self.emit(&token);
1415 }
1416 } else if self.peek(1).is_some_and(|b| b > 127) {
1417 let next_char = self.s[self.pos + 1..].chars().next();
1419 if next_char.is_some_and(|c| c.is_alphabetic() || c == '_') {
1420 let start = self.pos;
1421 self.pos += 1; while !self.at_end() {
1423 if self.bytes[self.pos] == b'#' || self.bytes[self.pos] == b'@' {
1424 self.pos += 1;
1425 continue;
1426 }
1427 let rest = &self.s[self.pos..];
1428 match rest.chars().next() {
1429 Some(c) if c.is_alphanumeric() || c == '_' => {
1430 self.pos += c.len_utf8();
1431 }
1432 _ => break,
1433 }
1434 }
1435 let token = self.s[start..self.pos].to_string();
1436 if self.maybe_consume_alias_next() {
1437 continue;
1438 }
1439 if self.config.replace_digits {
1440 self.emit(&apply_replace_digits(&token));
1441 } else {
1442 self.emit(&token);
1443 }
1444 } else {
1445 if self.maybe_consume_alias_next() {
1446 continue;
1447 }
1448 self.space();
1449 self.result.push('@');
1450 self.pos += 1;
1451 self.last_was_placeholder = false;
1452 self.result.push(' ');
1453 }
1454 } else if self.peek(1) == Some(b'>') {
1455 if self.maybe_consume_alias_next() {
1457 continue;
1458 }
1459 self.emit("@>");
1460 self.pos += 2;
1461 self.result.push(' ');
1462 } else {
1463 if self.maybe_consume_alias_next() {
1465 continue;
1466 }
1467 self.space();
1468 self.result.push('@');
1469 self.pos += 1;
1470 self.last_was_placeholder = false;
1471 self.result.push(' ');
1472 }
1473 }
1474
1475 b':' => {
1477 match self.peek(1) {
1478 Some(b':') => {
1479 if self.maybe_consume_alias_next() {
1481 continue;
1482 }
1483 self.space();
1484 self.result.push_str("::");
1485 self.pos += 2;
1486 self.last_was_placeholder = false;
1487 self.result.push(' ');
1488 }
1489 Some(b'=') => {
1490 if self.maybe_consume_alias_next() {
1492 continue;
1493 }
1494 self.space();
1495 self.result.push_str(":=");
1496 self.pos += 2;
1497 self.last_was_placeholder = false;
1498 self.result.push(' ');
1499 }
1500 Some(b)
1501 if b.is_ascii_alphanumeric() || b == b'_' || b == b'#' || b == b'@' =>
1502 {
1503 let start = self.pos;
1506 self.pos += 1; while !self.at_end()
1509 && (is_ident_char(self.bytes[self.pos])
1510 || self.bytes[self.pos] == b'.')
1511 {
1512 self.pos += 1;
1513 }
1514 let token = &self.s[start..self.pos];
1515 if self.maybe_consume_alias_next() {
1516 continue;
1517 }
1518 self.emit(token);
1519 if token.ends_with('.') {
1523 self.result.push(' ');
1524 }
1525 }
1526 Some(b) if b > 127 => {
1527 let next_char = self.s[self.pos + 1..].chars().next();
1531 if next_char.is_some_and(|c| c.is_alphabetic() || c == '_') {
1532 let start = self.pos;
1533 self.pos += 1; while !self.at_end() {
1535 if self.bytes[self.pos] == b'#'
1539 || self.bytes[self.pos] == b'@'
1540 || self.bytes[self.pos] == b'.'
1541 {
1542 self.pos += 1;
1543 continue;
1544 }
1545 let rest = &self.s[self.pos..];
1546 match rest.chars().next() {
1547 Some(c) if c.is_alphanumeric() || c == '_' => {
1548 self.pos += c.len_utf8();
1549 }
1550 _ => break,
1551 }
1552 }
1553 let token = &self.s[start..self.pos];
1554 if self.maybe_consume_alias_next() {
1555 continue;
1556 }
1557 self.emit(token);
1558 if token.ends_with('.') {
1562 self.result.push(' ');
1563 }
1564 } else {
1565 if self.maybe_consume_alias_next() {
1566 continue;
1567 }
1568 self.space();
1569 self.result.push(':');
1570 self.pos += 1;
1571 self.last_was_placeholder = false;
1572 self.result.push(' ');
1573 }
1574 }
1575 _ => {
1576 if self.maybe_consume_alias_next() {
1578 continue;
1579 }
1580 self.space();
1581 self.result.push(':');
1582 self.pos += 1;
1583 self.last_was_placeholder = false;
1584 self.result.push(' ');
1585 }
1586 }
1587 }
1588
1589 b'-' => {
1591 match self.peek(1) {
1592 Some(b'-') => {
1593 self.pos += 2;
1595 self.skip_line_comment();
1596 }
1597 Some(b'>') if self.peek(2) == Some(b'>') => {
1598 if self.maybe_consume_alias_next() {
1600 continue;
1601 }
1602 self.emit("->>");
1603 self.pos += 3;
1604 self.result.push(' ');
1605 if self.config.keep_json_path {
1606 self.pending_json_path = true;
1607 }
1608 }
1609 Some(b'>') => {
1610 if self.maybe_consume_alias_next() {
1612 continue;
1613 }
1614 self.emit("->");
1615 self.pos += 2;
1616 self.result.push(' ');
1617 if self.config.keep_json_path {
1618 self.pending_json_path = true;
1619 }
1620 }
1621 Some(b) if b.is_ascii_digit() => {
1622 {
1626 self.pos += 1; self.consume_number();
1628 if self.maybe_consume_alias_next() {
1629 continue;
1630 }
1631 self.emit_placeholder();
1632 }
1633 }
1634 Some(b'.')
1635 if self.peek(2).is_some()
1636 && !self.peek(2).is_some_and(|d| d.is_ascii_digit()) =>
1637 {
1638 if self.maybe_consume_alias_next() {
1649 continue;
1650 }
1651 self.space();
1652 self.result.push_str("-.");
1653 self.pos += 2;
1654 self.last_was_placeholder = false;
1655 self.result.push(' ');
1656 if !self.at_end() {
1659 let c_len = self.s[self.pos..]
1660 .chars()
1661 .next()
1662 .map_or(1, |c| c.len_utf8());
1663 let after_c = self.pos + c_len;
1664 if after_c < self.bytes.len()
1665 && self.bytes[after_c].is_ascii_digit()
1666 {
1667 self.pos = after_c;
1669 self.consume_number();
1670 self.emit_placeholder();
1671 } else {
1672 let n = c_len.min(self.bytes.len() - self.pos);
1675 let leaked = self.s[self.pos..self.pos + n].to_owned();
1676 self.pos += n;
1677 self.result.push_str(&leaked);
1678 self.result.push(' ');
1679 }
1680 }
1681 }
1682 Some(b'.') if self.peek(2).is_some_and(|d| d.is_ascii_digit()) => {
1683 self.pos += 2; self.consume_number();
1687 if self.maybe_consume_alias_next() {
1688 continue;
1689 }
1690 self.emit_placeholder();
1691 }
1692 _ => {
1693 if self.maybe_consume_alias_next() {
1694 continue;
1695 }
1696 self.space();
1697 self.result.push('-');
1698 self.pos += 1;
1699 self.last_was_placeholder = false;
1700 self.result.push(' ');
1701 }
1702 }
1703 }
1704
1705 b'+' => {
1707 if self.maybe_consume_alias_next() {
1710 continue;
1711 }
1712 self.space();
1713 self.result.push('+');
1714 self.pos += 1;
1715 self.last_was_placeholder = false;
1716 self.result.push(' ');
1717 }
1718
1719 b'?' => {
1721 let next = self.peek(1);
1722 match next {
1723 Some(b'|') if self.peek(2) != Some(b'|') => {
1724 if self.maybe_consume_alias_next() {
1726 continue;
1727 }
1728 self.emit("?|");
1729 self.pos += 2;
1730 self.result.push(' ');
1731 }
1732 Some(b'&') => {
1733 if self.maybe_consume_alias_next() {
1735 continue;
1736 }
1737 self.emit("?&");
1738 self.pos += 2;
1739 self.result.push(' ');
1740 }
1741 _ => {
1742 if self.maybe_consume_alias_next() {
1749 continue;
1750 }
1751 if matches!(self.dbms, DbmsKind::Postgresql) {
1752 self.space();
1753 self.result.push('?');
1754 self.last_was_assign = false;
1755 } else {
1757 self.emit_placeholder();
1758 }
1759 self.pos += 1;
1760 }
1761 }
1762 }
1763
1764 b'<' => {
1766 let next = self.peek(1);
1767 if self.maybe_consume_alias_next() {
1768 continue;
1769 }
1770 match next {
1771 Some(b'@') => {
1774 let next2_is_ident = self
1775 .peek(2)
1776 .is_some_and(|c| c.is_ascii_alphanumeric() || c == b'_');
1777 if matches!(self.dbms, DbmsKind::Postgresql) || !next2_is_ident {
1778 self.emit("<@");
1779 self.pos += 2;
1780 self.result.push(' ');
1781 } else {
1782 self.space();
1784 self.result.push('<');
1785 self.pos += 1;
1786 self.result.push(' ');
1787 }
1788 }
1789 Some(b'>') => {
1790 self.emit("<>");
1791 self.pos += 2;
1792 self.result.push(' ');
1793 }
1794 Some(b'=') => {
1795 self.emit("<=");
1796 self.pos += 2;
1797 self.result.push(' ');
1798 }
1799 _ => {
1800 self.space();
1801 self.result.push('<');
1802 self.pos += 1;
1803 self.last_was_placeholder = false;
1804 self.result.push(' ');
1805 }
1806 }
1807 }
1808
1809 b'>' => {
1811 if self.maybe_consume_alias_next() {
1812 continue;
1813 }
1814 if self.peek(1) == Some(b'=') {
1815 self.emit(">=");
1816 self.pos += 2;
1817 } else {
1818 self.space();
1819 self.result.push('>');
1820 self.pos += 1;
1821 self.last_was_placeholder = false;
1822 }
1823 self.result.push(' ');
1824 }
1825
1826 b'=' => {
1828 if self.maybe_consume_alias_next() {
1829 continue;
1830 }
1831 self.space();
1832 self.result.push('=');
1833 self.pos += 1;
1834 self.last_was_placeholder = false;
1835 self.result.push(' ');
1836 self.last_was_assign = true;
1837 continue; }
1839
1840 b'!' => {
1842 if self.maybe_consume_alias_next() {
1843 continue;
1844 }
1845 if self.peek(1) == Some(b'=') {
1846 self.emit("!=");
1847 self.pos += 2;
1848 self.result.push(' ');
1849 } else if self.peek(1) == Some(b'~') {
1850 if self.peek(2) == Some(b'*') {
1851 self.emit("!~*");
1852 self.pos += 3;
1853 } else {
1854 self.emit("!~");
1855 self.pos += 2;
1856 }
1857 self.result.push(' ');
1858 } else {
1859 self.space();
1860 self.result.push('!');
1861 self.pos += 1;
1862 self.last_was_placeholder = false;
1863 self.result.push(' ');
1864 }
1865 }
1866
1867 b'|' => {
1869 if self.maybe_consume_alias_next() {
1870 continue;
1871 }
1872 self.space();
1874 self.result.push('|');
1875 self.pos += 1;
1876 self.last_was_placeholder = false;
1877 self.result.push(' ');
1878 }
1879
1880 b'&' => {
1882 if self.maybe_consume_alias_next() {
1883 continue;
1884 }
1885 self.space();
1886 self.result.push('&');
1887 self.pos += 1;
1888 self.last_was_placeholder = false;
1889 self.result.push(' ');
1890 }
1891
1892 b'~' => {
1894 if self.maybe_consume_alias_next() {
1895 continue;
1896 }
1897 if self.peek(1) == Some(b'*') {
1898 self.emit("~*");
1899 self.pos += 2;
1900 } else {
1901 self.space();
1902 self.result.push('~');
1903 self.pos += 1;
1904 self.last_was_placeholder = false;
1905 }
1906 self.result.push(' ');
1907 }
1908 b'^' => {
1909 if self.maybe_consume_alias_next() {
1910 continue;
1911 }
1912 self.space();
1913 self.result.push('^');
1914 self.pos += 1;
1915 self.last_was_placeholder = false;
1916 self.result.push(' ');
1917 }
1918
1919 b'*' => {
1921 if self.maybe_consume_alias_next() {
1922 continue;
1923 }
1924 self.space();
1925 self.result.push('*');
1926 self.pos += 1;
1927 self.last_was_placeholder = false;
1928 self.result.push(' ');
1929 }
1930
1931 b'/' => {
1933 if self.maybe_consume_alias_next() {
1934 continue;
1935 }
1936 self.space();
1937 self.result.push('/');
1938 self.pos += 1;
1939 self.last_was_placeholder = false;
1940 self.result.push(' ');
1941 }
1942
1943 b if b > 127
1945 && self.s[self.pos..]
1946 .chars()
1947 .next()
1948 .is_some_and(|c| c.is_whitespace()) =>
1949 {
1950 let c = self.s[self.pos..].chars().next().unwrap_or(' ');
1951 self.pos += c.len_utf8();
1952 self.skip_whitespace();
1953 if self.before_as_len.is_none() {
1954 self.space();
1955 }
1956 }
1957
1958 _ if is_ident_start(b) || b > 127 => {
1960 let start = self.pos;
1961 while !self.at_end() {
1963 let b = self.bytes[self.pos];
1964 if b > 127 {
1965 let c = self.s[self.pos..].chars().next();
1968 if c.is_some_and(|c| c.is_whitespace()) {
1969 break;
1970 }
1971 self.pos += c.map_or(1, |c| c.len_utf8());
1972 } else if is_ident_char(b) || b == b'.' {
1973 self.pos += 1;
1974 } else {
1975 break;
1976 }
1977 }
1978 let token = &self.s[start..self.pos];
1979 self.emit_identifier(token);
1980 if token.ends_with('.') && !self.at_end() {
1983 self.result.push(' ');
1984 }
1985 }
1986
1987 _ => {
1989 let c = self.s[self.pos..].chars().next().unwrap_or(' ');
1990 if self.maybe_consume_alias_next() {
1991 self.pos += c.len_utf8();
1992 continue;
1993 }
1994 self.result.push(c);
1995 self.pos += c.len_utf8();
1996 }
1997 }
1998 }
1999 }
2000
2001 fn finalize(mut self) -> String {
2002 while self.result.ends_with(' ') {
2004 self.result.pop();
2005 }
2006 self.result
2007 }
2008}
2009
2010fn try_match_pure_group(bytes: &[u8], open: u8, close: u8, i: usize) -> Option<usize> {
2013 let n = bytes.len();
2014 if i >= n || bytes[i] != open {
2015 return None;
2016 }
2017 let mut k = i + 1;
2018 if k < n && bytes[k] == b' ' {
2019 k += 1;
2020 }
2021 if k >= n || bytes[k] != b'?' {
2022 return None;
2023 }
2024 k += 1;
2025 loop {
2026 if k < n && bytes[k] == b' ' {
2027 k += 1;
2028 }
2029 if k >= n {
2030 return None;
2031 }
2032 if bytes[k] == close {
2033 return Some(k + 1);
2034 }
2035 if bytes[k] == b',' {
2037 k += 1;
2038 if k < n && bytes[k] == b' ' {
2039 k += 1;
2040 }
2041 }
2042 if k < n && bytes[k] == b'?' {
2043 k += 1;
2044 } else {
2045 return None;
2046 }
2047 }
2048}
2049
2050fn collapse_grouped_values(s: &str, obfuscation_mode: SqlObfuscationMode) -> String {
2053 let bytes = s.as_bytes();
2054 let n = bytes.len();
2055 let mut result = String::with_capacity(n);
2056 let mut i = 0;
2057
2058 while i < n {
2059 if bytes[i] == b'(' {
2061 if let Some(end) = try_match_pure_group(bytes, b'(', b')', i) {
2062 result.push_str("( ? )");
2063 i = end;
2064 continue;
2065 }
2066 }
2067 if bytes[i] == b'[' {
2069 if let Some(end) = try_match_pure_group(bytes, b'[', b']', i) {
2070 result.push_str("[ ? ]");
2071 i = end;
2072 continue;
2073 }
2074 }
2075 if bytes[i] < 128 {
2077 result.push(bytes[i] as char);
2078 i += 1;
2079 } else {
2080 let c = s[i..].chars().next().unwrap_or('\u{FFFD}');
2081 result.push(c);
2082 i += c.len_utf8();
2083 }
2084 }
2085
2086 let result = collapse_multi_values(&result);
2087 #[allow(deprecated)]
2088 if matches!(obfuscation_mode, SqlObfuscationMode::Unspecified) {
2089 collapse_limit_two_args(&result)
2093 } else {
2094 result
2095 }
2096}
2097
2098fn collapse_multi_values(s: &str) -> String {
2102 let mut result = String::with_capacity(s.len());
2104 let mut remaining = s;
2105
2106 while let Some(c) = remaining.chars().next() {
2107 const VALUES_KW: &str = "VALUES";
2108 const VALUES_TAIL: &str = " ( ? )";
2109 const VALUES_FULL: &str = "VALUES ( ? )";
2110
2111 let matches_values_pattern = remaining.get(..VALUES_FULL.len()).is_some_and(|head| {
2113 head.get(..VALUES_KW.len())
2114 .is_some_and(|kw| kw.eq_ignore_ascii_case(VALUES_KW))
2115 && head
2116 .get(VALUES_KW.len()..)
2117 .is_some_and(|tail| tail == VALUES_TAIL)
2118 });
2119
2120 if matches_values_pattern {
2121 let prev_ok = result.is_empty()
2123 || matches!(result.chars().last(), Some(' ') | Some('(') | Some('\n'));
2124
2125 if prev_ok {
2126 result.push_str(&remaining[..VALUES_FULL.len()]);
2128 remaining = &remaining[VALUES_FULL.len()..];
2129
2130 loop {
2132 if let Some(rest) = remaining.strip_prefix(", ( ? )") {
2133 remaining = rest;
2134 } else if let Some(rest) = remaining.strip_prefix(" ( ? )") {
2135 remaining = rest;
2136 } else if let Some(rest) = remaining.strip_prefix(" ()") {
2137 remaining = rest;
2138 } else {
2139 break;
2140 }
2141 }
2142 continue;
2143 }
2144 }
2145
2146 result.push(c);
2147 remaining = &remaining[c.len_utf8()..];
2148 }
2149 result
2150}
2151
2152fn collapse_limit_two_args(s: &str) -> String {
2154 let mut result = String::with_capacity(s.len());
2156 let mut remaining = s;
2157
2158 while !remaining.is_empty() {
2159 if remaining.len() >= 9 {
2161 let rb = remaining.as_bytes();
2162 const PREFIX: &[u8] = b"LIMIT ?";
2163 if rb[..PREFIX.len()].eq_ignore_ascii_case(PREFIX) {
2164 let skip =
2166 if remaining.len() >= 10 && rb[7] == b',' && rb[8] == b' ' && rb[9] == b'?' {
2167 Some(10) } else if rb[7] == b' ' && rb[8] == b'?' {
2169 Some(9) } else {
2171 None
2172 };
2173 if let Some(skip_len) = skip {
2174 let prev_ok = result.is_empty()
2176 || matches!(
2177 result.as_bytes().last(),
2178 Some(b' ') | Some(b'(') | Some(b'\n')
2179 );
2180 if prev_ok {
2181 result.push_str(&remaining[..7]); remaining = &remaining[skip_len..];
2183 continue;
2184 }
2185 }
2186 }
2187 }
2188 let c = remaining.chars().next().unwrap_or(' ');
2189 result.push(c);
2190 remaining = &remaining[c.len_utf8()..];
2191 }
2192 result
2193}
2194
2195pub fn obfuscate_sql(s: &str, config: &SqlObfuscateConfig, dbms: DbmsKind) -> String {
2197 if s.is_empty() {
2198 return String::new();
2199 }
2200 let mut tokenizer = Tokenizer::new(s, config, dbms);
2201 tokenizer.process();
2202 let raw = tokenizer.finalize();
2203 #[allow(deprecated)]
2206 let should_collapse = matches!(
2207 config.obfuscation_mode,
2208 SqlObfuscationMode::Unspecified | SqlObfuscationMode::ObfuscateAndNormalize
2209 );
2210 if should_collapse {
2211 collapse_grouped_values(&raw, config.obfuscation_mode)
2212 } else {
2213 raw
2214 }
2215}
2216
2217pub fn obfuscate_sql_string(s: &str) -> String {
2219 obfuscate_sql(s, &SqlObfuscateConfig::default(), DbmsKind::Generic)
2220}
2221
2222pub fn obfuscate_sql_string_normalized(s: &str) -> String {
2227 let obfuscated = obfuscate_sql_string(s);
2228 normalize_plan_sql(&obfuscated)
2229}
2230
2231fn normalize_plan_sql(s: &str) -> String {
2232 let mut result = String::with_capacity(s.len());
2233 let mut chars = s.chars().peekable();
2234
2235 while let Some(c) = chars.next() {
2236 match c {
2237 '`' => {
2238 let identifier: String = chars.by_ref().take_while(|&c| c != '`').collect();
2240 result.push_str(&identifier);
2241
2242 if chars.peek() == Some(&'.') {
2244 chars.next(); result.push_str(if chars.peek() == Some(&'`') {
2246 " . "
2247 } else {
2248 "."
2249 });
2250 }
2251 }
2252 '(' => {
2253 result.push('(');
2254 if chars.peek().is_some_and(|&c| c != ' ') {
2255 result.push(' ');
2256 }
2257 }
2258 ')' => {
2259 if result.as_bytes().last().is_some_and(|&b| b != b' ') {
2260 result.push(' ');
2261 }
2262 result.push(')');
2263 }
2264 ':' if chars.peek() == Some(&':') => {
2265 chars.next(); if result.as_bytes().last().is_some_and(|&b| b != b' ') {
2267 result.push(' ');
2268 }
2269 result.push_str("::");
2270 if chars.peek().is_some_and(|&c| c != ' ') {
2271 result.push(' ');
2272 }
2273 }
2274 _ => result.push(c),
2275 }
2276 }
2277 result
2278}
2279
2280#[cfg(test)]
2281mod tests {
2282 use super::{DbmsKind, SqlObfuscateConfig, SqlObfuscationMode};
2283
2284 #[test]
2285 fn test_sql_obfuscation() {
2286 let mut panic = None;
2287 let err = CASES
2288 .iter()
2289 .enumerate()
2290 .filter_map(|(i, (input, output))| {
2291 let err =
2292 match std::panic::catch_unwind(|| test_sql_obfuscation_case(input, output)) {
2293 Ok(r) => r,
2294 Err(p) => {
2295 panic = Some(p);
2296 eprintln!("panicked case {i}\n\tinput: {input}\n\n");
2297 return None;
2298 }
2299 }
2300 .err()?;
2301 Some(format!("failed case {i}\n\terr: {err}\n"))
2302 })
2303 .collect::<String>();
2304 if !err.is_empty() {
2305 if panic.is_none() {
2306 panic!("{err}")
2307 } else {
2308 eprintln!("{err}")
2309 }
2310 }
2311 if let Some(p) = panic {
2312 std::panic::resume_unwind(p);
2313 }
2314 }
2315
2316 fn test_sql_obfuscation_case(input: &str, output: &str) -> anyhow::Result<()> {
2317 let got = super::obfuscate_sql_string(input);
2318 if output != got {
2319 anyhow::bail!("expected {output:?}\n\tgot: {got:?}")
2320 }
2321 Ok(())
2322 }
2323
2324 #[test]
2325 fn test_sql_obfuscation_normalized() {
2326 let mut panic = None;
2327 let err = NORMALIZED_CASES
2328 .iter()
2329 .enumerate()
2330 .filter_map(|(i, (input, output))| {
2331 let err = match std::panic::catch_unwind(|| {
2332 test_sql_obfuscation_normalized_case(input, output)
2333 }) {
2334 Ok(r) => r,
2335 Err(p) => {
2336 panic = Some(p);
2337 eprintln!("panicked normalized case {i}\n\tinput: {input}\n\n");
2338 return None;
2339 }
2340 }
2341 .err()?;
2342 Some(format!("failed normalized case {i}\n\terr: {err}\n"))
2343 })
2344 .collect::<String>();
2345 if !err.is_empty() {
2346 if panic.is_none() {
2347 panic!("{err}")
2348 } else {
2349 eprintln!("{err}")
2350 }
2351 }
2352 if let Some(p) = panic {
2353 std::panic::resume_unwind(p);
2354 }
2355 }
2356
2357 fn test_sql_obfuscation_normalized_case(input: &str, output: &str) -> anyhow::Result<()> {
2358 let got = super::obfuscate_sql_string_normalized(input);
2359 if output != got {
2360 anyhow::bail!("expected {output:?}\n\tgot: {got:?}")
2361 }
2362 Ok(())
2363 }
2364
2365 #[test]
2366 fn test_keep_identifier_quotation() {
2367 let config = SqlObfuscateConfig {
2368 keep_identifier_quotation: true,
2369 ..Default::default()
2370 };
2371 let got = super::obfuscate_sql(
2372 r#"SELECT * FROM "users" WHERE id = 1 AND name = 'test'"#,
2373 &config,
2374 DbmsKind::Generic,
2375 );
2376 let expected = "SELECT * FROM users WHERE id = ? AND name = ?";
2378 assert_eq!(got, expected, "keep_identifier_quotation: got {got:?}");
2379 }
2380
2381 #[test]
2382 fn test_remove_space_between_parentheses() {
2383 let config = SqlObfuscateConfig {
2384 remove_space_between_parentheses: true,
2385 ..Default::default()
2386 };
2387 let got = super::obfuscate_sql(
2388 "SELECT * FROM users WHERE id = ? AND (name = 'test' OR name = 'test2')",
2389 &config,
2390 DbmsKind::Generic,
2391 );
2392 let expected = "SELECT * FROM users WHERE id = ? AND ( name = ? OR name = ? )";
2394 assert_eq!(
2395 got, expected,
2396 "remove_space_between_parentheses: got {got:?}"
2397 );
2398 }
2399
2400 #[test]
2401 fn test_keep_positional_parameter() {
2402 let config = SqlObfuscateConfig {
2404 keep_positional_parameter: true,
2405 ..Default::default()
2406 };
2407 let got = super::obfuscate_sql(
2408 "SELECT * FROM users WHERE id = ? AND name = $1 and id = $2",
2409 &config,
2410 DbmsKind::Generic,
2411 );
2412 let expected = "SELECT * FROM users WHERE id = ? AND name = ? and id = ?";
2415 assert_eq!(
2416 got, expected,
2417 "keep_positional_parameter: got {got:?}, expected {expected:?}"
2418 );
2419 }
2420
2421 const NORMALIZED_CASES: &[(&str, &str)] = &[
2422 ("'60'::double precision", "? :: double precision"),
2424 ("'dogfood'::text", "? :: text"),
2425 ("'15531'::tid", "? :: tid"),
2426 ("(query <> 'dogfood'::text)", "( query <> ? :: text )"),
2427 ("(foo != ?)", "( foo != ? )"),
2429 ("((a >= ?) AND (b < ?))", "( ( a >= ? ) AND ( b < ? ) )"),
2430 ("?::double precision", "? :: double precision"),
2432 ("(query <> ?::text)", "( query <> ? :: text )"),
2433 ("`id`", "id"),
2435 (
2436 "(`sbtest`.`sbtest1`.`id` between ? and ?)",
2437 "( sbtest . sbtest1 . id between ? and ? )",
2438 ),
2439 (
2441 "(`sbtest`.`sbtest1`.`id` between 5016 and 5115)",
2442 "( sbtest . sbtest1 . id between ? and ? )",
2443 ),
2444 ("(query <> 'dogfood'::text)", "( query <> ? :: text )"),
2445 ("'60'::double precision", "? :: double precision"),
2446 ];
2447
2448 const CASES: &[(&str, &str)] = &[
2449 ("", ""),
2450 (" ", ""),
2451 (" ", ""),
2452 ("罿", "罿"),
2453 ("罿潯", "罿潯"),
2454 ("罿潯罿潯罿潯罿潯罿潯", "罿潯罿潯罿潯罿潯罿潯"),
2455 ("'abc1287681964'", "?"),
2456 ("-- comment", ""),
2457 ("---", ""),
2458 ("1 - 2", "? - ?"),
2459 (
2460 "SELECT * FROM TABLE WHERE userId = 'abc1287681964'",
2461 "SELECT * FROM TABLE WHERE userId = ?",
2462 ),
2463 (
2465 "SELECT * FROM TABLE WHERE userId = 'it''s a string'",
2466 "SELECT * FROM TABLE WHERE userId = ?",
2467 ),
2468 (
2469 "SELECT * FROM TABLE WHERE userId IN ('a', 'b', 'c')",
2470 "SELECT * FROM TABLE WHERE userId IN ( ? )",
2471 ),
2472 (
2473 "SELECT * FROM TABLE WHERE userId = 'abc1287681964' ORDER BY FOO DESC",
2474 "SELECT * FROM TABLE WHERE userId = ? ORDER BY FOO DESC",
2475 ),
2476 (
2479 "SELECT * FROM foo LEFT JOIN bar ON 'backslash\\' = foo.b WHERE foo.name = 'String'",
2480 "SELECT * FROM foo LEFT JOIN bar ON ? = foo.b WHERE foo.name = ?",
2481 ),
2482 (
2483 "SELECT * FROM foo LEFT JOIN bar ON 'backslash\\' = foo.b LEFT JOIN bar2 ON 'backslash2\\' = foo.b2 WHERE foo.name = 'String'",
2484 "SELECT * FROM foo LEFT JOIN bar ON ? = foo.b LEFT JOIN bar2 ON ? = foo.b2 WHERE foo.name = ?",
2485 ),
2486 (
2489 "SELECT * FROM foo LEFT JOIN bar ON 'embedded \\'quote\\' in string' = foo.b WHERE foo.name = 'String'",
2490 "SELECT * FROM foo LEFT JOIN bar ON ? = foo.b WHERE foo.name = ?",
2491 ),
2492 (
2493 "SELECT * FROM TABLE JOIN SOMETHING ON TABLE.foo = SOMETHING.bar",
2494 "SELECT * FROM TABLE JOIN SOMETHING ON TABLE.foo = SOMETHING.bar",
2495 ),
2496 (
2497 "CREATE TABLE \"VALUE\"",
2498 "CREATE TABLE VALUE",
2499 ),
2500 (
2501 "INSERT INTO \"VALUE\" (\"column\") VALUES (\'ljahklshdlKASH\')",
2502 "INSERT INTO VALUE ( column ) VALUES ( ? )",
2503 ),
2504 (
2505 "INSERT INTO \"VALUE\" (\"col1\",\"col2\",\"col3\") VALUES (\'blah\',12983,X'ff')",
2506 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2507 ),
2508 (
2509 "INSERT INTO \"VALUE\" (\"col1\", \"col2\", \"col3\") VALUES (\'blah\',12983,X'ff')",
2510 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2511 ),
2512 (
2513 "INSERT INTO VALUE (col1,col2,col3) VALUES (\'blah\',12983,X'ff')",
2514 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2515 ),
2516 (
2517 "INSERT INTO VALUE (col1,col2,col3) VALUES (12983,X'ff',\'blah\')",
2518 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2519 ),
2520 (
2521 "INSERT INTO VALUE (col1,col2,col3) VALUES (X'ff',\'blah\',12983)",
2522 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2523 ),
2524 (
2525 "INSERT INTO VALUE (col1,col2,col3) VALUES ('a',\'b\',1)",
2526 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2527 ),
2528 (
2529 "INSERT INTO VALUE (col1, col2, col3) VALUES ('a',\'b\',1)",
2530 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2531 ),
2532 (
2533 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ('a',\'b\',1)",
2534 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2535 ),
2536 (
2537 "INSERT INTO VALUE (col1,col2,col3) VALUES ('a', \'b\' ,1)",
2538 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2539 ),
2540 (
2541 "INSERT INTO VALUE (col1, col2, col3) VALUES ('a', \'b\', 1)",
2542 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2543 ),
2544 (
2545 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ('a', \'b\', 1)",
2546 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2547 ),
2548 (
2549 "INSERT INTO VALUE (col1,col2,col3) VALUES (X'ff',\'罿潯罿潯罿潯罿潯罿潯\',12983)",
2550 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2551 ),
2552 (
2553 "INSERT INTO VALUE (col1,col2,col3) VALUES (X'ff',\'罿\',12983)",
2554 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2555 ),
2556 (
2558 "SELECT 3 AS NUCLEUS_TYPE,A0.ID,A0.\"NAME\" FROM \"VALUE\" A0",
2559 "SELECT ?, A0.ID, A0. NAME FROM VALUE A0",
2560 ),
2561 (
2562 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > .9999",
2563 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2564 ),
2565 (
2566 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > 0.9999",
2567 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2568 ),
2569 (
2570 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > -0.9999",
2571 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2572 ),
2573 (
2574 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > -1e6",
2575 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2576 ),
2577 (
2578 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > +1e6",
2579 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > + ?",
2580 ),
2581 (
2582 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > +255",
2583 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > + ?",
2584 ),
2585 (
2586 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > +6.34F",
2587 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > + ? F",
2588 ),
2589 (
2590 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > +6f",
2591 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > + ? f",
2592 ),
2593 (
2594 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > +0.5D",
2595 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > + ? D",
2596 ),
2597 (
2598 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > -1d",
2599 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ? d",
2600 ),
2601 (
2602 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > x'ff'",
2603 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2604 ),
2605 (
2606 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > X'ff'",
2607 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2608 ),
2609 (
2610 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > 0xff",
2611 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2612 ),
2613 (
2614 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> \'\'",
2615 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ?",
2616 ),
2617 (
2618 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> \' \'",
2619 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ?",
2620 ),
2621 (
2622 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> \' \'",
2623 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ?",
2624 ),
2625 (
2627 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ' x '",
2628 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ?",
2629 ),
2630 (
2631 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ' x x'",
2632 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ?",
2633 ),
2634 (
2635 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> \'5,123\'",
2636 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ?",
2637 ),
2638 (
2640 "CREATE TABLE S_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))",
2641 "CREATE TABLE S_H2 ( id INTEGER not ? PRIMARY KEY ( id ) )",
2642 ),
2643 (
2644 "CREATE TABLE S_H2 ( id INTEGER not NULL, PRIMARY KEY ( id ) )",
2645 "CREATE TABLE S_H2 ( id INTEGER not ? PRIMARY KEY ( id ) )",
2646 ),
2647 (
2648 "SELECT * FROM TABLE WHERE name = 'O''Brady'",
2649 "SELECT * FROM TABLE WHERE name = ?",
2650 ),
2651 (
2652 "INSERT INTO visits VALUES (2, 8, '2013-01-02', 'rabies shot')",
2653 "INSERT INTO visits VALUES ( ? )",
2654 ),
2655 (
2656 "SELECT * FROM TABLE WHERE userId = ',' and foo=foo.bar",
2657 "SELECT * FROM TABLE WHERE userId = ? and foo = foo.bar",
2658 ),
2659 (
2660 "SELECT * FROM TABLE WHERE userId = ','||foo.bar",
2661 "SELECT * FROM TABLE WHERE userId = ? | | foo.bar",
2662 ),
2663 (
2665 "SELECT * FROM t WHERE y IN (:protocols) AND x IN (:sites)",
2666 "SELECT * FROM t WHERE y IN ( :protocols ) AND x IN ( :sites )",
2667 ),
2668 (
2670 "INSERT INTO user (id, username) VALUES ('Fred','Smith'), ('John','Smith'), ('Michael','Smith'), ('Robert','Smith');",
2671 "INSERT INTO user ( id, username ) VALUES ( ? )",
2672 ),
2673 (
2675 "INSERT INTO `qual-aa`.issues (alert0, alert1) VALUES (NULL, NULL)",
2676 "INSERT INTO qual-aa . issues ( alert0, alert1 ) VALUES ( ? )",
2677 ),
2678 (
2680 "select !+2",
2681 "select ! + ?",
2682 ),
2683 (
2687 "SELECT 5*s1 FROM t4",
2688 "SELECT ? * s1 FROM t4",
2689 ),
2690 (
2691 "(SELECT 5*s1 FROM t4 UNION SELECT 77 FROM t5)",
2692 "( SELECT ? * s1 FROM t4 UNION SELECT ? FROM t5 )",
2693 ),
2694 (
2696 "WHERE ROW(5*t2.s1,77)=(SELECT 5*s1 FROM t4 UNION SELECT 77 FROM (SELECT * FROM t5))",
2697 "WHERE ROW ( ? * t2.s1, ? ) = ( SELECT ? * s1 FROM t4 UNION SELECT ? FROM ( SELECT * FROM t5 ) )",
2698 ),
2699 (
2701 "UPDATE user_dash_pref SET json_prefs = %(json_prefs)s, modified = '2015-08-27' WHERE user_id = %(user_id)s AND url = %(url)s",
2702 "UPDATE user_dash_pref SET json_prefs = ? modified = ? WHERE user_id = ? AND url = ?",
2703 ),
2704 (
2706 "UPDATE t SET a = 1, b = 2, c = 3",
2707 "UPDATE t SET a = ? b = ? c = ?",
2708 ),
2709 (
2711 "SELECT set_config('foo', bar, FALSE)",
2712 "SELECT set_config ( ? bar, ? )",
2713 ),
2714 (":ჸ", ":ჸ"),
2716 ("%ჸ", "?"),
2718 ("%C", "?"),
2720 (".ჸ", ". ჸ"),
2722 ("0!(2", "? ! ( ?"),
2724 ("0(($0", "? ( ("),
2726 ("0D", "? D"),
2728 ("@ᏤᏤ", "@ᏤᏤ"),
2730 ("ჸ*", "ჸ*"),
2732 (".*", ". *"),
2734 ("table.*", "table.*"),
2735 ("(ჷ", "( ჷ"),
2737 ("2%$2", "? % ?"),
2738 ];
2739
2740 #[test]
2741 fn test_normalize_only() {
2742 let config = SqlObfuscateConfig {
2743 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
2744 ..Default::default()
2745 };
2746 let cases = &[
2747 (
2749 "SELECT * FROM users WHERE id = 1",
2750 "SELECT * FROM users WHERE id = 1",
2751 ),
2752 (
2754 "SELECT * FROM users WHERE id = 1 AND name = 'test'",
2755 "SELECT * FROM users WHERE id = 1 AND name = 'test'",
2756 ),
2757 (
2759 "-- comment\n/* comment */\nSELECT id as id, name as n FROM users123 WHERE id in (1,2,3)",
2760 "SELECT id as id, name as n FROM users123 WHERE id in ( 1, 2, 3 )",
2761 ),
2762 (
2764 "WITH users AS (SELECT * FROM people) SELECT * FROM users",
2765 "WITH users AS ( SELECT * FROM people ) SELECT * FROM users",
2766 ),
2767 (
2769 "SELECT * FROM users WHERE id = 1 AND address = $1 and id = $2 AND deleted IS NULL AND active is TRUE",
2770 "SELECT * FROM users WHERE id = 1 AND address = $1 and id = $2 AND deleted IS NULL AND active is TRUE",
2771 ),
2772 (
2776 "SELECT * FROM users WHERE id = 1;",
2777 "SELECT * FROM users WHERE id = 1",
2778 ),
2779 ];
2780 for (input, expected) in cases {
2781 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
2782 assert_eq!(got, *expected, "normalize_only input={input:?}");
2783 }
2784 }
2785
2786 #[test]
2787 fn test_normalize_only_keep_trailing_semi() {
2788 let config = SqlObfuscateConfig {
2789 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
2790 keep_trailing_semicolon: true,
2791 ..Default::default()
2792 };
2793 let got = super::obfuscate_sql(
2794 "SELECT * FROM users WHERE id = 1 AND name = 'test';",
2795 &config,
2796 DbmsKind::Generic,
2797 );
2798 let expected = "SELECT * FROM users WHERE id = 1 AND name = 'test';";
2799 assert_eq!(
2800 got, expected,
2801 "normalize_only+keep_trailing_semicolon: {got:?}"
2802 );
2803 }
2804
2805 #[test]
2806 fn test_normalize_only_keep_identifier_quotation() {
2807 let config = SqlObfuscateConfig {
2808 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
2809 keep_identifier_quotation: true,
2810 ..Default::default()
2811 };
2812 let got = super::obfuscate_sql(
2813 r#"SELECT * FROM "users" WHERE id = 1 AND name = 'test'"#,
2814 &config,
2815 DbmsKind::Generic,
2816 );
2817 let expected = r#"SELECT * FROM "users" WHERE id = 1 AND name = 'test'"#;
2818 assert_eq!(
2819 got, expected,
2820 "normalize_only+keep_identifier_quotation: {got:?}"
2821 );
2822 }
2823
2824 #[test]
2825 fn test_with_cte_stripping() {
2826 let config = SqlObfuscateConfig::default();
2828 let cases = &[
2829 (
2831 "WITH sales AS (SELECT x FROM t WHERE id = 1) SELECT * FROM sales",
2832 "WITH sales SELECT x FROM t WHERE id = ? ) SELECT * FROM sales",
2833 ),
2834 (
2836 "WITH T1 AS (SELECT a FROM t1 WHERE id = 1), T2 AS (SELECT b FROM t2) SELECT * FROM T1",
2837 "WITH T1 SELECT a FROM t1 WHERE id = ? ) T2 SELECT b FROM t2 ) SELECT * FROM T1",
2838 ),
2839 ];
2840 for (input, expected) in cases {
2841 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
2842 assert_eq!(got, *expected, "with_cte_stripping input={input:?}");
2843 }
2844 }
2845
2846 #[test]
2847 fn test_double_quoted_string_value_quantize() {
2848 let config = SqlObfuscateConfig::default();
2850 let cases = &[
2851 (
2853 r#"update Orders set created = "2019-05-24 00:26:17", gross = 30.28"#,
2854 "update Orders set created = ? gross = ?",
2855 ),
2856 (
2858 r#"update Orders set payment_type = "eventbrite""#,
2859 "update Orders set payment_type = ?",
2860 ),
2861 (
2863 r#"SELECT * FROM "users" WHERE id = 1"#,
2864 r#"SELECT * FROM users WHERE id = ?"#,
2865 ),
2866 ];
2867 for (input, expected) in cases {
2868 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
2869 assert_eq!(got, *expected, "double_quoted_value input={input:?}");
2870 }
2871 }
2872
2873 #[test]
2874 fn test_normalize_only_dollar_func() {
2875 let config = SqlObfuscateConfig {
2877 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
2878 ..Default::default()
2879 };
2880 let got = super::obfuscate_sql(
2881 "SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users",
2882 &config,
2883 DbmsKind::Generic,
2884 );
2885 let expected = "SELECT $func$INSERT INTO table VALUES ( 'a', 1, 2 )$func$ FROM users";
2886 assert_eq!(got, expected, "normalize_only dollar_func: {got:?}");
2887 }
2888
2889 #[test]
2890 fn test_dollar_quoted_func_trivial_collapse() {
2891 let config = SqlObfuscateConfig {
2893 dollar_quoted_func: true,
2894 replace_digits: true,
2895 ..Default::default()
2896 };
2897 let got = super::obfuscate_sql(
2898 "SELECT * FROM users123 WHERE id = $tag$1$tag$",
2899 &config,
2900 DbmsKind::Generic,
2901 );
2902 let expected = "SELECT * FROM users? WHERE id = ?";
2903 assert_eq!(
2904 got, expected,
2905 "dollar_quoted_func trivial collapse: {got:?}"
2906 );
2907 }
2908
2909 #[test]
2910 fn test_obfuscate_only_keeps_quotes_and_semi() {
2911 let config = SqlObfuscateConfig {
2913 obfuscation_mode: SqlObfuscationMode::ObfuscateOnly,
2914 ..Default::default()
2915 };
2916 let got = super::obfuscate_sql(
2917 r#"SELECT "table"."field" FROM "table" WHERE "table"."otherfield" = $? AND "table"."thirdfield" = $?;"#,
2918 &config,
2919 DbmsKind::Generic,
2920 );
2921 let expected = r#"SELECT "table"."field" FROM "table" WHERE "table"."otherfield" = $? AND "table"."thirdfield" = $?;"#;
2922 assert_eq!(got, expected, "obfuscate_only keeps quotes/$/semi: {got:?}");
2923 }
2924
2925 #[test]
2926 fn test_obfuscate_only_dollar_quoted_func_no_collapse() {
2927 let config = SqlObfuscateConfig {
2929 obfuscation_mode: SqlObfuscationMode::ObfuscateOnly,
2930 dollar_quoted_func: true,
2931 ..Default::default()
2932 };
2933 let got = super::obfuscate_sql(
2934 "SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users",
2935 &config,
2936 DbmsKind::Generic,
2937 );
2938 let expected = "SELECT $func$INSERT INTO table VALUES (?, ?, ?)$func$ FROM users";
2939 assert_eq!(
2940 got, expected,
2941 "obfuscate_only dollar_quoted_func no collapse: {got:?}"
2942 );
2943 }
2944
2945 #[test]
2946 fn test_normalize_only_procedure() {
2947 let config = SqlObfuscateConfig {
2948 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
2949 ..Default::default()
2950 };
2951 let got = super::obfuscate_sql(
2952 "CREATE PROCEDURE TestProc AS BEGIN UPDATE users SET name = 'test' WHERE id = 1 END",
2953 &config,
2954 DbmsKind::Generic,
2955 );
2956 let expected =
2957 "CREATE PROCEDURE TestProc AS BEGIN UPDATE users SET name = 'test' WHERE id = 1 END";
2958 assert_eq!(got, expected, "normalize_only+procedure: {got:?}");
2959 }
2960
2961 #[test]
2962 fn test_q41() {
2963 let config = SqlObfuscateConfig::default();
2964 let input = "SELECT * FROM public.table ( array [ ROW ( array [ 'magic', 'foo',";
2965 let mut tok = super::Tokenizer::new(input, &config, DbmsKind::Generic);
2967 tok.process();
2968 let raw = tok.finalize();
2969 eprintln!("RAW: {raw:?}");
2970 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
2971 let expected = "SELECT * FROM public.table ( array [ ROW ( array [ ?";
2972 assert_eq!(got, expected, "q41: {got:?}");
2973 }
2974
2975 #[test]
2978 fn test_pg_json_operators_7() {
2979 let got = super::obfuscate_sql(
2981 "select * from users where user.custom ? 'foo'",
2982 &SqlObfuscateConfig::default(),
2983 DbmsKind::Postgresql,
2984 );
2985 let expected = "select * from users where user.custom ? ?";
2986 assert_eq!(got, expected, "pg_json_7: {got:?}");
2987 }
2988
2989 #[test]
2990 fn test_quantizer_90() {
2991 let config = SqlObfuscateConfig::default();
2993 let got = super::obfuscate_sql(
2994 "SELECT * FROM dbo.Items WHERE id = 1 or /*!obfuscation*/ 1 = 1",
2995 &config,
2996 DbmsKind::Generic,
2997 );
2998 let expected = "SELECT * FROM dbo.Items WHERE id = ? or ? = ?";
2999 assert_eq!(got, expected, "q90: {got:?}");
3000 }
3001
3002 #[test]
3003 fn test_cassandra_nested_dates() {
3004 let config = SqlObfuscateConfig::default();
3006 let got = super::obfuscate_sql(
3007 "SELECT TO_DATE(TO_CHAR(TO_DATE(bar.h,?),?),?) FROM t",
3008 &config,
3009 DbmsKind::Generic,
3010 );
3011 let expected = "SELECT TO_DATE ( TO_CHAR ( TO_DATE ( bar.h, ? ) ) ) FROM t";
3012 assert_eq!(got, expected, "cassandra_nested_dates: {got:?}");
3013 }
3014
3015 #[test]
3016 fn test_cassandra_pipe_concat() {
3017 let config = SqlObfuscateConfig::default();
3019 let got = super::obfuscate_sql("SELECT a ||?|| b FROM t", &config, DbmsKind::Generic);
3020 let expected = "SELECT a | | ? | | b FROM t";
3021 assert_eq!(got, expected, "cassandra_pipe: {got:?}");
3022 }
3023
3024 const SUITE_CASES: &[(&str, &str)] = &[
3026 ("SELECT username AS person FROM users WHERE id=4", "SELECT username FROM users WHERE id = ?"),
3028 ("autovacuum: VACUUM ANALYZE fake.table", "autovacuum : VACUUM ANALYZE fake.table"),
3030 ("autovacuum: VACUUM ANALYZE fake.table_downtime", "autovacuum : VACUUM ANALYZE fake.table_downtime"),
3032 ("autovacuum: VACUUM fake.big_table (to prevent wraparound)", "autovacuum : VACUUM fake.big_table ( to prevent wraparound )"),
3034 ("SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users", "SELECT ? FROM users"),
3036 ("\n/* Multi-line comment */\nSELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1 BEGIN INSERT INTO owners (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 1, NULL, '2011-08-30 05:22:57') COMMIT", "SELECT * FROM clients WHERE ( clients.first_name = ? ) LIMIT ? BEGIN INSERT INTO owners ( created_at, first_name, locked, orders_count, updated_at ) VALUES ( ? ) COMMIT"),
3038 ("\n/* Multi-line comment */\nSELECT * FROM clients WHERE (clients.first_name = 'Andy') limit 1 BEGIN INSERT INTO owners (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 1, NULL, '2011-08-30 05:22:57') COMMIT", "SELECT * FROM clients WHERE ( clients.first_name = ? ) limit ? BEGIN INSERT INTO owners ( created_at, first_name, locked, orders_count, updated_at ) VALUES ( ? ) COMMIT"),
3040 ("\n-- Single line comment\n-- Another single line comment\n-- Another another single line comment\nGRANT USAGE, DELETE ON SCHEMA datadog TO datadog", "GRANT USAGE, DELETE ON SCHEMA datadog TO datadog"),
3042 ("\n/*\nMulti-line comment\nwith line breaks\n*/\n/* Two multi-line comments with\nline breaks */\nSELECT clients.* FROM clients INNER JOIN posts ON posts.author_id = author.id AND posts.published = 't'", "SELECT clients.* FROM clients INNER JOIN posts ON posts.author_id = author.id AND posts.published = ?"),
3044 ("CREATE TRIGGER dogwatcher SELECT ON w1 BEFORE (UPDATE d1 SET (c1, c2, c3) = (c1 + 1, c2 + 1, c3 + 1))", "CREATE TRIGGER dogwatcher SELECT ON w1 BEFORE ( UPDATE d1 SET ( c1, c2, c3 ) = ( c1 + ? c2 + ? c3 + ? ) )"),
3046 ("\n-- Testing table value constructor SQL expression\nSELECT * FROM (VALUES (1, 'dog')) AS d (id, animal)", "SELECT * FROM ( VALUES ( ? ) ) ( id, animal )"),
3048 ("ALTER TABLE table DROP COLUMN column", "ALTER TABLE table DROP COLUMN column"),
3050 ("REVOKE ALL ON SCHEMA datadog FROM datadog", "REVOKE ALL ON SCHEMA datadog FROM datadog"),
3052 ("TRUNCATE TABLE datadog", "TRUNCATE TABLE datadog"),
3054 ("\n-- Testing explicit table SQL expression\nWITH T1 AS (SELECT PNO , PNAME , COLOR , WEIGHT , CITY FROM P WHERE CITY = 'London'),\nT2 AS (SELECT PNO, PNAME, COLOR, WEIGHT, CITY, 2 * WEIGHT AS NEW_WEIGHT, 'Oslo' AS NEW_CITY FROM T1),\nT3 AS ( SELECT PNO , PNAME, COLOR, NEW_WEIGHT AS WEIGHT, NEW_CITY AS CITY FROM T2),\nT4 AS ( TABLE P EXCEPT CORRESPONDING TABLE T1)\nTABLE T4 UNION CORRESPONDING TABLE T3", "WITH T1 SELECT PNO, PNAME, COLOR, WEIGHT, CITY FROM P WHERE CITY = ? ) T2 SELECT PNO, PNAME, COLOR, WEIGHT, CITY, ? * WEIGHT, ? FROM T1 ), T3 SELECT PNO, PNAME, COLOR, NEW_WEIGHT, NEW_CITY FROM T2 ), T4 TABLE P EXCEPT CORRESPONDING TABLE T1 ) TABLE T4 UNION CORRESPONDING TABLE T3"),
3056 ("SELECT Codi , Nom_CA AS Nom, Descripció_CAT AS Descripció FROM ProtValAptitud WHERE Vigent=1 ORDER BY Ordre, Codi", "SELECT Codi, Nom_CA, Descripció_CAT FROM ProtValAptitud WHERE Vigent = ? ORDER BY Ordre, Codi"),
3058 (" SELECT dbo.Treballadors_ProtCIE_AntecedentsPatologics.IdTreballadorsProtCIE_AntecedentsPatologics, dbo.ProtCIE.Codi As CodiProtCIE, Treballadors_ProtCIE_AntecedentsPatologics.Año, dbo.ProtCIE.Nom_ES, dbo.ProtCIE.Nom_CA FROM dbo.Treballadors_ProtCIE_AntecedentsPatologics WITH (NOLOCK) INNER JOIN dbo.ProtCIE WITH (NOLOCK) ON dbo.Treballadors_ProtCIE_AntecedentsPatologics.CodiProtCIE = dbo.ProtCIE.Codi WHERE Treballadors_ProtCIE_AntecedentsPatologics.IdTreballador = 12345 ORDER BY Treballadors_ProtCIE_AntecedentsPatologics.Año DESC, dbo.ProtCIE.Codi ", "SELECT dbo.Treballadors_ProtCIE_AntecedentsPatologics.IdTreballadorsProtCIE_AntecedentsPatologics, dbo.ProtCIE.Codi, Treballadors_ProtCIE_AntecedentsPatologics.Año, dbo.ProtCIE.Nom_ES, dbo.ProtCIE.Nom_CA FROM dbo.Treballadors_ProtCIE_AntecedentsPatologics WITH ( NOLOCK ) INNER JOIN dbo.ProtCIE WITH ( NOLOCK ) ON dbo.Treballadors_ProtCIE_AntecedentsPatologics.CodiProtCIE = dbo.ProtCIE.Codi WHERE Treballadors_ProtCIE_AntecedentsPatologics.IdTreballador = ? ORDER BY Treballadors_ProtCIE_AntecedentsPatologics.Año DESC, dbo.ProtCIE.Codi"),
3060 ("select top 100 percent IdTrebEmpresa as [IdTrebEmpresa], CodCli as [Client], NOMEMP as [Nom Client], Baixa as [Baixa], CASE WHEN IdCentreTreball IS NULL THEN '-' ELSE CONVERT(VARCHAR(8),IdCentreTreball) END as [Id Centre], CASE WHEN NOMESTAB IS NULL THEN '-' ELSE NOMESTAB END as [Nom Centre], TIPUS as [Tipus Lloc], CASE WHEN IdLloc IS NULL THEN '-' ELSE CONVERT(VARCHAR(8),IdLloc) END as [Id Lloc], CASE WHEN NomLlocComplert IS NULL THEN '-' ELSE NomLlocComplert END as [Lloc Treball], CASE WHEN DesLloc IS NULL THEN '-' ELSE DesLloc END as [Descripció], IdLlocTreballUnic as [Id Únic] From ( SELECT '-' AS TIPUS, dbo.Treb_Empresa.IdTrebEmpresa, dbo.Treb_Empresa.IdTreballador, dbo.Treb_Empresa.CodCli, dbo.Clients.NOMEMP, dbo.Treb_Empresa.Baixa, dbo.Treb_Empresa.IdCentreTreball, dbo.Cli_Establiments.NOMESTAB, null AS IdLloc, null AS NomLlocComplert, dbo.Treb_Empresa.DataInici, dbo.Treb_Empresa.DataFi, CASE WHEN dbo.Treb_Empresa.DesLloc IS NULL THEN '' ELSE dbo.Treb_Empresa.DesLloc END DesLloc, dbo.Treb_Empresa.IdLlocTreballUnic FROM dbo.Clients WITH (NOLOCK) INNER JOIN dbo.Treb_Empresa WITH (NOLOCK) ON dbo.Clients.CODCLI = dbo.Treb_Empresa.CodCli LEFT OUTER JOIN dbo.Cli_Establiments WITH (NOLOCK) ON dbo.Cli_Establiments.Id_ESTAB_CLI = dbo.Treb_Empresa.IdCentreTreball AND dbo.Cli_Establiments.CODCLI = dbo.Treb_Empresa.CodCli WHERE dbo.Treb_Empresa.IdTreballador = 64376 AND Treb_Empresa.IdTecEIRLLlocTreball IS NULL AND IdMedEIRLLlocTreball IS NULL AND IdLlocTreballTemporal IS NULL UNION ALL SELECT 'AV. RIESGO' AS TIPUS, dbo.Treb_Empresa.IdTrebEmpresa, dbo.Treb_Empresa.IdTreballador, dbo.Treb_Empresa.CodCli, dbo.Clients.NOMEMP, dbo.Treb_Empresa.Baixa, dbo.Treb_Empresa.IdCentreTreball, dbo.Cli_Establiments.NOMESTAB, dbo.Treb_Empresa.IdTecEIRLLlocTreball AS IdLloc, dbo.fn_NomLlocComposat(dbo.Treb_Empresa.IdTecEIRLLlocTreball) AS NomLlocComplert, dbo.Treb_Empresa.DataInici, dbo.Treb_Empresa.DataFi, CASE WHEN dbo.Treb_Empresa.DesLloc IS NULL THEN '' ELSE dbo.Treb_Empresa.DesLloc END DesLloc, dbo.Treb_Empresa.IdLlocTreballUnic FROM dbo.Clients WITH (NOLOCK) INNER JOIN dbo.Treb_Empresa WITH (NOLOCK) ON dbo.Clients.CODCLI = dbo.Treb_Empresa.CodCli LEFT OUTER JOIN dbo.Cli_Establiments WITH (NOLOCK) ON dbo.Cli_Establiments.Id_ESTAB_CLI = dbo.Treb_Empresa.IdCentreTreball AND dbo.Cli_Establiments.CODCLI = dbo.Treb_Empresa.CodCli WHERE (dbo.Treb_Empresa.IdTreballador = 64376) AND (NOT (dbo.Treb_Empresa.IdTecEIRLLlocTreball IS NULL)) UNION ALL SELECT 'EXTERNA' AS TIPUS, dbo.Treb_Empresa.IdTrebEmpresa, dbo.Treb_Empresa.IdTreballador, dbo.Treb_Empresa.CodCli, dbo.Clients.NOMEMP, dbo.Treb_Empresa.Baixa, dbo.Treb_Empresa.IdCentreTreball, dbo.Cli_Establiments.NOMESTAB, dbo.Treb_Empresa.IdMedEIRLLlocTreball AS IdLloc, dbo.fn_NomMedEIRLLlocComposat(dbo.Treb_Empresa.IdMedEIRLLlocTreball) AS NomLlocComplert, dbo.Treb_Empresa.DataInici, dbo.Treb_Empresa.DataFi, CASE WHEN dbo.Treb_Empresa.DesLloc IS NULL THEN '' ELSE dbo.Treb_Empresa.DesLloc END DesLloc, dbo.Treb_Empresa.IdLlocTreballUnic FROM dbo.Clients WITH (NOLOCK) INNER JOIN dbo.Treb_Empresa WITH (NOLOCK) ON dbo.Clients.CODCLI = dbo.Treb_Empresa.CodCli LEFT OUTER JOIN dbo.Cli_Establiments WITH (NOLOCK) ON dbo.Cli_Establiments.Id_ESTAB_CLI = dbo.Treb_Empresa.IdCentreTreball AND dbo.Cli_Establiments.CODCLI = dbo.Treb_Empresa.CodCli WHERE (dbo.Treb_Empresa.IdTreballador = 64376) AND (Treb_Empresa.IdTecEIRLLlocTreball IS NULL) AND (NOT (dbo.Treb_Empresa.IdMedEIRLLlocTreball IS NULL)) UNION ALL SELECT 'TEMPORAL' AS TIPUS, dbo.Treb_Empresa.IdTrebEmpresa, dbo.Treb_Empresa.IdTreballador, dbo.Treb_Empresa.CodCli, dbo.Clients.NOMEMP, dbo.Treb_Empresa.Baixa, dbo.Treb_Empresa.IdCentreTreball, dbo.Cli_Establiments.NOMESTAB, dbo.Treb_Empresa.IdLlocTreballTemporal AS IdLloc, dbo.Lloc_Treball_Temporal.NomLlocTreball AS NomLlocComplert, dbo.Treb_Empresa.DataInici, dbo.Treb_Empresa.DataFi, CASE WHEN dbo.Treb_Empresa.DesLloc IS NULL THEN '' ELSE dbo.Treb_Empresa.DesLloc END DesLloc, dbo.Treb_Empresa.IdLlocTreballUnic FROM dbo.Clients WITH (NOLOCK) INNER JOIN dbo.Treb_Empresa WITH (NOLOCK) ON dbo.Clients.CODCLI = dbo.Treb_Empresa.CodCli INNER JOIN dbo.Lloc_Treball_Temporal WITH (NOLOCK) ON dbo.Treb_Empresa.IdLlocTreballTemporal = dbo.Lloc_Treball_Temporal.IdLlocTreballTemporal LEFT OUTER JOIN dbo.Cli_Establiments WITH (NOLOCK) ON dbo.Cli_Establiments.Id_ESTAB_CLI = dbo.Treb_Empresa.IdCentreTreball AND dbo.Cli_Establiments.CODCLI = dbo.Treb_Empresa.CodCli WHERE dbo.Treb_Empresa.IdTreballador = 64376 AND Treb_Empresa.IdTecEIRLLlocTreball IS NULL AND IdMedEIRLLlocTreball IS NULL ) as taula Where 1=0 ", "select top ? percent IdTrebEmpresa, CodCli, NOMEMP, Baixa, CASE WHEN IdCentreTreball IS ? THEN ? ELSE CONVERT ( VARCHAR ( ? ) IdCentreTreball ) END, CASE WHEN NOMESTAB IS ? THEN ? ELSE NOMESTAB END, TIPUS, CASE WHEN IdLloc IS ? THEN ? ELSE CONVERT ( VARCHAR ( ? ) IdLloc ) END, CASE WHEN NomLlocComplert IS ? THEN ? ELSE NomLlocComplert END, CASE WHEN DesLloc IS ? THEN ? ELSE DesLloc END, IdLlocTreballUnic From ( SELECT ?, dbo.Treb_Empresa.IdTrebEmpresa, dbo.Treb_Empresa.IdTreballador, dbo.Treb_Empresa.CodCli, dbo.Clients.NOMEMP, dbo.Treb_Empresa.Baixa, dbo.Treb_Empresa.IdCentreTreball, dbo.Cli_Establiments.NOMESTAB, ?, ?, dbo.Treb_Empresa.DataInici, dbo.Treb_Empresa.DataFi, CASE WHEN dbo.Treb_Empresa.DesLloc IS ? THEN ? ELSE dbo.Treb_Empresa.DesLloc END DesLloc, dbo.Treb_Empresa.IdLlocTreballUnic FROM dbo.Clients WITH ( NOLOCK ) INNER JOIN dbo.Treb_Empresa WITH ( NOLOCK ) ON dbo.Clients.CODCLI = dbo.Treb_Empresa.CodCli LEFT OUTER JOIN dbo.Cli_Establiments WITH ( NOLOCK ) ON dbo.Cli_Establiments.Id_ESTAB_CLI = dbo.Treb_Empresa.IdCentreTreball AND dbo.Cli_Establiments.CODCLI = dbo.Treb_Empresa.CodCli WHERE dbo.Treb_Empresa.IdTreballador = ? AND Treb_Empresa.IdTecEIRLLlocTreball IS ? AND IdMedEIRLLlocTreball IS ? AND IdLlocTreballTemporal IS ? UNION ALL SELECT ?, dbo.Treb_Empresa.IdTrebEmpresa, dbo.Treb_Empresa.IdTreballador, dbo.Treb_Empresa.CodCli, dbo.Clients.NOMEMP, dbo.Treb_Empresa.Baixa, dbo.Treb_Empresa.IdCentreTreball, dbo.Cli_Establiments.NOMESTAB, dbo.Treb_Empresa.IdTecEIRLLlocTreball, dbo.fn_NomLlocComposat ( dbo.Treb_Empresa.IdTecEIRLLlocTreball ), dbo.Treb_Empresa.DataInici, dbo.Treb_Empresa.DataFi, CASE WHEN dbo.Treb_Empresa.DesLloc IS ? THEN ? ELSE dbo.Treb_Empresa.DesLloc END DesLloc, dbo.Treb_Empresa.IdLlocTreballUnic FROM dbo.Clients WITH ( NOLOCK ) INNER JOIN dbo.Treb_Empresa WITH ( NOLOCK ) ON dbo.Clients.CODCLI = dbo.Treb_Empresa.CodCli LEFT OUTER JOIN dbo.Cli_Establiments WITH ( NOLOCK ) ON dbo.Cli_Establiments.Id_ESTAB_CLI = dbo.Treb_Empresa.IdCentreTreball AND dbo.Cli_Establiments.CODCLI = dbo.Treb_Empresa.CodCli WHERE ( dbo.Treb_Empresa.IdTreballador = ? ) AND ( NOT ( dbo.Treb_Empresa.IdTecEIRLLlocTreball IS ? ) ) UNION ALL SELECT ?, dbo.Treb_Empresa.IdTrebEmpresa, dbo.Treb_Empresa.IdTreballador, dbo.Treb_Empresa.CodCli, dbo.Clients.NOMEMP, dbo.Treb_Empresa.Baixa, dbo.Treb_Empresa.IdCentreTreball, dbo.Cli_Establiments.NOMESTAB, dbo.Treb_Empresa.IdMedEIRLLlocTreball, dbo.fn_NomMedEIRLLlocComposat ( dbo.Treb_Empresa.IdMedEIRLLlocTreball ), dbo.Treb_Empresa.DataInici, dbo.Treb_Empresa.DataFi, CASE WHEN dbo.Treb_Empresa.DesLloc IS ? THEN ? ELSE dbo.Treb_Empresa.DesLloc END DesLloc, dbo.Treb_Empresa.IdLlocTreballUnic FROM dbo.Clients WITH ( NOLOCK ) INNER JOIN dbo.Treb_Empresa WITH ( NOLOCK ) ON dbo.Clients.CODCLI = dbo.Treb_Empresa.CodCli LEFT OUTER JOIN dbo.Cli_Establiments WITH ( NOLOCK ) ON dbo.Cli_Establiments.Id_ESTAB_CLI = dbo.Treb_Empresa.IdCentreTreball AND dbo.Cli_Establiments.CODCLI = dbo.Treb_Empresa.CodCli WHERE ( dbo.Treb_Empresa.IdTreballador = ? ) AND ( Treb_Empresa.IdTecEIRLLlocTreball IS ? ) AND ( NOT ( dbo.Treb_Empresa.IdMedEIRLLlocTreball IS ? ) ) UNION ALL SELECT ?, dbo.Treb_Empresa.IdTrebEmpresa, dbo.Treb_Empresa.IdTreballador, dbo.Treb_Empresa.CodCli, dbo.Clients.NOMEMP, dbo.Treb_Empresa.Baixa, dbo.Treb_Empresa.IdCentreTreball, dbo.Cli_Establiments.NOMESTAB, dbo.Treb_Empresa.IdLlocTreballTemporal, dbo.Lloc_Treball_Temporal.NomLlocTreball, dbo.Treb_Empresa.DataInici, dbo.Treb_Empresa.DataFi, CASE WHEN dbo.Treb_Empresa.DesLloc IS ? THEN ? ELSE dbo.Treb_Empresa.DesLloc END DesLloc, dbo.Treb_Empresa.IdLlocTreballUnic FROM dbo.Clients WITH ( NOLOCK ) INNER JOIN dbo.Treb_Empresa WITH ( NOLOCK ) ON dbo.Clients.CODCLI = dbo.Treb_Empresa.CodCli INNER JOIN dbo.Lloc_Treball_Temporal WITH ( NOLOCK ) ON dbo.Treb_Empresa.IdLlocTreballTemporal = dbo.Lloc_Treball_Temporal.IdLlocTreballTemporal LEFT OUTER JOIN dbo.Cli_Establiments WITH ( NOLOCK ) ON dbo.Cli_Establiments.Id_ESTAB_CLI = dbo.Treb_Empresa.IdCentreTreball AND dbo.Cli_Establiments.CODCLI = dbo.Treb_Empresa.CodCli WHERE dbo.Treb_Empresa.IdTreballador = ? AND Treb_Empresa.IdTecEIRLLlocTreball IS ? AND IdMedEIRLLlocTreball IS ? ) Where ? = ?"),
3062 ("select IdHistLabAnt as [IdHistLabAnt], IdTreballador as [IdTreballador], Empresa as [Professió], Anys as [Anys], Riscs as [Riscos], Nom_CA AS [Prot CNO], Nom_ES as [Prot CNO Altre Idioma] From ( SELECT dbo.Treb_HistAnt.IdHistLabAnt, dbo.Treb_HistAnt.IdTreballador, dbo.Treb_HistAnt.Empresa, dbo.Treb_HistAnt.Anys, dbo.Treb_HistAnt.Riscs, dbo.Treb_HistAnt.CodiProtCNO, dbo.ProtCNO.Nom_ES, dbo.ProtCNO.Nom_CA FROM dbo.Treb_HistAnt WITH (NOLOCK) LEFT OUTER JOIN dbo.ProtCNO WITH (NOLOCK) ON dbo.Treb_HistAnt.CodiProtCNO = dbo.ProtCNO.Codi Where dbo.Treb_HistAnt.IdTreballador = 12345 ) as taula ", "select IdHistLabAnt, IdTreballador, Empresa, Anys, Riscs, Nom_CA, Nom_ES From ( SELECT dbo.Treb_HistAnt.IdHistLabAnt, dbo.Treb_HistAnt.IdTreballador, dbo.Treb_HistAnt.Empresa, dbo.Treb_HistAnt.Anys, dbo.Treb_HistAnt.Riscs, dbo.Treb_HistAnt.CodiProtCNO, dbo.ProtCNO.Nom_ES, dbo.ProtCNO.Nom_CA FROM dbo.Treb_HistAnt WITH ( NOLOCK ) LEFT OUTER JOIN dbo.ProtCNO WITH ( NOLOCK ) ON dbo.Treb_HistAnt.CodiProtCNO = dbo.ProtCNO.Codi Where dbo.Treb_HistAnt.IdTreballador = ? )"),
3064 ("SELECT Cli_Establiments.CODCLI, Cli_Establiments.Id_ESTAB_CLI As [Código Centro Trabajo], Cli_Establiments.CODIGO_CENTRO_AXAPTA As [Código C. Axapta], Cli_Establiments.NOMESTAB As [Nombre], Cli_Establiments.ADRECA As [Dirección], Cli_Establiments.CodPostal As [Código Postal], Cli_Establiments.Poblacio as [Población], Cli_Establiments.Provincia, Cli_Establiments.TEL As [Tel], Cli_Establiments.EMAIL As [EMAIL], Cli_Establiments.PERS_CONTACTE As [Contacto], Cli_Establiments.PERS_CONTACTE_CARREC As [Cargo Contacto], Cli_Establiments.NumTreb As [Plantilla], Cli_Establiments.Localitzacio As [Localización], Tipus_Activitat.CNAE, Tipus_Activitat.Nom_ES As [Nombre Actividad], ACTIVO AS [Activo] FROM Cli_Establiments LEFT OUTER JOIN Tipus_Activitat ON Cli_Establiments.Id_ACTIVITAT = Tipus_Activitat.IdActivitat Where CODCLI = '01234' AND CENTRE_CORRECTE = 3 AND ACTIVO = 5 ORDER BY Cli_Establiments.CODIGO_CENTRO_AXAPTA ", "SELECT Cli_Establiments.CODCLI, Cli_Establiments.Id_ESTAB_CLI, Cli_Establiments.CODIGO_CENTRO_AXAPTA, Cli_Establiments.NOMESTAB, Cli_Establiments.ADRECA, Cli_Establiments.CodPostal, Cli_Establiments.Poblacio, Cli_Establiments.Provincia, Cli_Establiments.TEL, Cli_Establiments.EMAIL, Cli_Establiments.PERS_CONTACTE, Cli_Establiments.PERS_CONTACTE_CARREC, Cli_Establiments.NumTreb, Cli_Establiments.Localitzacio, Tipus_Activitat.CNAE, Tipus_Activitat.Nom_ES, ACTIVO FROM Cli_Establiments LEFT OUTER JOIN Tipus_Activitat ON Cli_Establiments.Id_ACTIVITAT = Tipus_Activitat.IdActivitat Where CODCLI = ? AND CENTRE_CORRECTE = ? AND ACTIVO = ? ORDER BY Cli_Establiments.CODIGO_CENTRO_AXAPTA"),
3066 ("select * from dollarField$ as df from some$dollar$filled_thing$$;", "select * from dollarField$ from some$dollar$filled_thing$$"),
3068 ("select * from `構わない`;", "select * from 構わない"),
3070 ("select * from names where name like '�����';", "select * from names where name like ?"),
3072 ("select replacement from table where replacement = 'i�n�t�e��rspersed';", "select replacement from table where replacement = ?"),
3074 ("SELECT ('�');", "SELECT ( ? )"),
3076 ("REPLACE INTO sales_2019_07_01 (`itemID`, `date`, `qty`, `price`) VALUES ((SELECT itemID FROM item1001 WHERE `sku` = [sku]), CURDATE(), [qty], 0.00)", "REPLACE INTO sales_2019_07_01 ( itemID, date, qty, price ) VALUES ( ( SELECT itemID FROM item1001 WHERE sku = [ sku ] ), CURDATE ( ), [ qty ], ? )"),
3078 ("SELECT ddh19.name, ddt.tags FROM dd91219.host ddh19, dd21916.host_tags ddt WHERE ddh19.id = ddt.host_id AND ddh19.org_id = 2 AND ddh19.name = 'datadog'", "SELECT ddh19.name, ddt.tags FROM dd91219.host ddh19, dd21916.host_tags ddt WHERE ddh19.id = ddt.host_id AND ddh19.org_id = ? AND ddh19.name = ?"),
3080 ("SELECT ddu2.name, ddo.id10, ddk.app_key52 FROM dd3120.user ddu2, dd1931.orgs55 ddo, dd53819.keys ddk", "SELECT ddu2.name, ddo.id10, ddk.app_key52 FROM dd3120.user ddu2, dd1931.orgs55 ddo, dd53819.keys ddk"),
3082 ("SELECT daily_values1529.*, LEAST((5040000 - @runtot), value1830) AS value1830,\n(@runtot := @runtot + daily_values1529.value1830) AS total\nFROM (SELECT @runtot:=0) AS n,\ndaily_values1529 WHERE daily_values1529.subject_id = 12345 AND daily_values1592.subject_type = 'Skippity'\nAND (daily_values1529.date BETWEEN '2018-05-09' AND '2018-06-19') HAVING value >= 0 ORDER BY date", "SELECT daily_values1529.*, LEAST ( ( ? - @runtot ), value1830 ), ( @runtot := @runtot + daily_values1529.value1830 ) FROM ( SELECT @runtot := ? ), daily_values1529 WHERE daily_values1529.subject_id = ? AND daily_values1592.subject_type = ? AND ( daily_values1529.date BETWEEN ? AND ? ) HAVING value >= ? ORDER BY date"),
3084 ("WITH sales AS\n(SELECT sf2.*\n\tFROM gosalesdw28391.sls_order_method_dim AS md,\n\t\tgosalesdw1920.sls_product_dim391 AS pd190,\n\t\tgosalesdw3819.emp_employee_dim AS ed,\n\t\tgosalesdw3919.sls_sales_fact3819 AS sf2\n\tWHERE pd190.product_key = sf2.product_key\n\tAND pd190.product_number381 > 10000\n\tAND pd190.base_product_key > 30\n\tAND md.order_method_key = sf2.order_method_key8319\n\tAND md.order_method_code > 5\n\tAND ed.employee_key = sf2.employee_key\n\tAND ed.manager_code1 > 20),\ninventory3118 AS\n(SELECT if.*\n\tFROM gosalesdw1592.go_branch_dim AS bd3221,\n\tgosalesdw.dist_inventory_fact AS if\n\tWHERE if.branch_key = bd3221.branch_key\n\tAND bd3221.branch_code > 20)\nSELECT sales1828.product_key AS PROD_KEY,\nSUM(CAST (inventory3118.quantity_shipped AS BIGINT)) AS INV_SHIPPED3118,\nSUM(CAST (sales1828.quantity AS BIGINT)) AS PROD_QUANTITY,\nRANK() OVER ( ORDER BY SUM(CAST (sales1828.quantity AS BIGINT)) DESC) AS PROD_RANK\nFROM sales1828, inventory3118\nWHERE sales1828.product_key = inventory3118.product_key\nGROUP BY sales1828.product_key", "WITH sales SELECT sf2.* FROM gosalesdw28391.sls_order_method_dim, gosalesdw1920.sls_product_dim391, gosalesdw3819.emp_employee_dim, gosalesdw3919.sls_sales_fact3819 WHERE pd190.product_key = sf2.product_key AND pd190.product_number381 > ? AND pd190.base_product_key > ? AND md.order_method_key = sf2.order_method_key8319 AND md.order_method_code > ? AND ed.employee_key = sf2.employee_key AND ed.manager_code1 > ? ) inventory3118 SELECT if.* FROM gosalesdw1592.go_branch_dim, gosalesdw.dist_inventory_fact WHERE if.branch_key = bd3221.branch_key AND bd3221.branch_code > ? ) SELECT sales1828.product_key, SUM ( CAST ( inventory3118.quantity_shipped ) ), SUM ( CAST ( sales1828.quantity ) ), RANK ( ) OVER ( ORDER BY SUM ( CAST ( sales1828.quantity ) ) DESC ) FROM sales1828, inventory3118 WHERE sales1828.product_key = inventory3118.product_key GROUP BY sales1828.product_key"),
3086 ("SELECT \"table\".\"field\" FROM \"table\" WHERE \"table\".\"otherfield\" = $? AND \"table\".\"thirdfield\" = $?;", "SELECT table . field FROM table WHERE table . otherfield = ? AND table . thirdfield = ?"),
3088 ("select * from users where id = 42", "select * from users where id = ?"),
3090 ("select * from users where float = .43422", "select * from users where float = ?"),
3092 ("SELECT host, status FROM ec2_status WHERE org_id = 42", "SELECT host, status FROM ec2_status WHERE org_id = ?"),
3094 ("SELECT host, status FROM ec2_status WHERE org_id=42", "SELECT host, status FROM ec2_status WHERE org_id = ?"),
3096 ("-- get user \n--\n select * \n from users \n where\n id = 214325346", "select * from users where id = ?"),
3098 ("SELECT * FROM `host` WHERE `id` IN (42, 43) /*comment with parameters,host:localhost,url:controller#home,id:FF005:00CAA*/", "SELECT * FROM host WHERE id IN ( ? )"),
3100 ("SELECT `host`.`address` FROM `host` WHERE org_id=42", "SELECT host . address FROM host WHERE org_id = ?"),
3102 ("SELECT \"host\".\"address\" FROM \"host\" WHERE org_id=42", "SELECT host . address FROM host WHERE org_id = ?"),
3104 ("SELECT * FROM host WHERE id IN (42, 43) /*\n\t\t\tmultiline comment with parameters,\n\t\t\thost:localhost,url:controller#home,id:FF005:00CAA\n\t\t\t*/", "SELECT * FROM host WHERE id IN ( ? )"),
3106 ("UPDATE user_dash_pref SET json_prefs = %(json_prefs)s, modified = '2015-08-27 22:10:32.492912' WHERE user_id = %(user_id)s AND url = %(url)s", "UPDATE user_dash_pref SET json_prefs = ? modified = ? WHERE user_id = ? AND url = ?"),
3108 ("SELECT DISTINCT host.id AS host_id FROM host JOIN host_alias ON host_alias.host_id = host.id WHERE host.org_id = %(org_id_1)s AND host.name NOT IN (%(name_1)s) AND host.name IN (%(name_2)s, %(name_3)s, %(name_4)s, %(name_5)s)", "SELECT DISTINCT host.id FROM host JOIN host_alias ON host_alias.host_id = host.id WHERE host.org_id = ? AND host.name NOT IN ( ? ) AND host.name IN ( ? )"),
3110 ("SELECT org_id, metric_key FROM metrics_metadata WHERE org_id = %(org_id)s AND metric_key = ANY(array[75])", "SELECT org_id, metric_key FROM metrics_metadata WHERE org_id = ? AND metric_key = ANY ( array [ ? ] )"),
3112 ("SELECT org_id, metric_key FROM metrics_metadata WHERE org_id = %(org_id)s AND metric_key = ANY(array[21, 25, 32])", "SELECT org_id, metric_key FROM metrics_metadata WHERE org_id = ? AND metric_key = ANY ( array [ ? ] )"),
3114 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 1", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3116 ("SELECT articles.* FROM articles WHERE articles.id = 1 limit 1", "SELECT articles.* FROM articles WHERE articles.id = ? limit ?"),
3118 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 1, 20", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3120 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 1, 20;", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3122 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 1, 20;", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3124 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 15,20;", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3126 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 1;", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3128 ("SELECT articles.* FROM articles WHERE (articles.created_at BETWEEN '2016-10-31 23:00:00.000000' AND '2016-11-01 23:00:00.000000')", "SELECT articles.* FROM articles WHERE ( articles.created_at BETWEEN ? AND ? )"),
3130 ("SELECT articles.* FROM articles WHERE (articles.created_at BETWEEN $1 AND $2)", "SELECT articles.* FROM articles WHERE ( articles.created_at BETWEEN ? AND ? )"),
3132 ("SELECT articles.* FROM articles WHERE (articles.published != true)", "SELECT articles.* FROM articles WHERE ( articles.published != ? )"),
3134 ("SELECT articles.* FROM articles WHERE (title = 'guides.rubyonrails.org')", "SELECT articles.* FROM articles WHERE ( title = ? )"),
3136 ("SELECT articles.* FROM articles WHERE ( title = ? ) AND ( author = ? )", "SELECT articles.* FROM articles WHERE ( title = ? ) AND ( author = ? )"),
3138 ("SELECT articles.* FROM articles WHERE ( title = :title )", "SELECT articles.* FROM articles WHERE ( title = :title )"),
3140 ("SELECT articles.* FROM articles WHERE ( title = @title )", "SELECT articles.* FROM articles WHERE ( title = @title )"),
3142 ("SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at) HAVING sum(price) > 100", "SELECT date ( created_at ), sum ( price ) FROM orders GROUP BY date ( created_at ) HAVING sum ( price ) > ?"),
3144 ("SELECT * FROM articles WHERE id > 10 ORDER BY id asc LIMIT 20", "SELECT * FROM articles WHERE id > ? ORDER BY id asc LIMIT ?"),
3146 ("SELECT clients.* FROM clients INNER JOIN posts ON posts.author_id = author.id AND posts.published = 't'", "SELECT clients.* FROM clients INNER JOIN posts ON posts.author_id = author.id AND posts.published = ?"),
3148 ("SELECT articles.* FROM articles WHERE articles.id IN (1, 3, 5)", "SELECT articles.* FROM articles WHERE articles.id IN ( ? )"),
3150 ("SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1 BEGIN INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 1, NULL, '2011-08-30 05:22:57') COMMIT", "SELECT * FROM clients WHERE ( clients.first_name = ? ) LIMIT ? BEGIN INSERT INTO clients ( created_at, first_name, locked, orders_count, updated_at ) VALUES ( ? ) COMMIT"),
3152 ("SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 15, 25 BEGIN INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 1, NULL, '2011-08-30 05:22:57') COMMIT", "SELECT * FROM clients WHERE ( clients.first_name = ? ) LIMIT ? BEGIN INSERT INTO clients ( created_at, first_name, locked, orders_count, updated_at ) VALUES ( ? ) COMMIT"),
3154 ("SAVEPOINT \"s139956586256192_x1\"", "SAVEPOINT ?"),
3156 ("INSERT INTO user (id, username) VALUES ('Fred','Smith'), ('John','Smith'), ('Michael','Smith'), ('Robert','Smith');", "INSERT INTO user ( id, username ) VALUES ( ? )"),
3158 ("CREATE KEYSPACE Excelsior WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 3};", "CREATE KEYSPACE Excelsior WITH replication = ?"),
3160 ("SELECT \"webcore_page\".\"id\" FROM \"webcore_page\" WHERE \"webcore_page\".\"slug\" = %s ORDER BY \"webcore_page\".\"path\" ASC LIMIT 1", "SELECT webcore_page . id FROM webcore_page WHERE webcore_page . slug = ? ORDER BY webcore_page . path ASC LIMIT ?"),
3162 ("SELECT server_table.host AS host_id FROM table#.host_tags as server_table WHERE server_table.host_id = 50", "SELECT server_table.host FROM table#.host_tags WHERE server_table.host_id = ?"),
3164 ("INSERT INTO delayed_jobs (attempts, created_at, failed_at, handler, last_error, locked_at, locked_by, priority, queue, run_at, updated_at) VALUES (0, '2016-12-04 17:09:59', NULL, '--- !ruby/object:Delayed::PerformableMethod\nobject: !ruby/object:Item\n store:\n - a simple string\n - an \\'escaped \\' string\n - another \\'escaped\\' string\n - 42\n string: a string with many \\\\\\\\\\'escapes\\\\\\\\\\'\nmethod_name: :show_store\nargs: []\n', NULL, NULL, NULL, 0, NULL, '2016-12-04 17:09:59', '2016-12-04 17:09:59')", "INSERT INTO delayed_jobs ( attempts, created_at, failed_at, handler, last_error, locked_at, locked_by, priority, queue, run_at, updated_at ) VALUES ( ? )"),
3166 ("SELECT name, pretty_print(address) FROM people;", "SELECT name, pretty_print ( address ) FROM people"),
3168 ("* SELECT * FROM fake_data(1, 2, 3);", "* SELECT * FROM fake_data ( ? )"),
3170 ("CREATE FUNCTION add(integer, integer) RETURNS integer\n AS 'select $1 + $2;'\n LANGUAGE SQL\n IMMUTABLE\n RETURNS NULL ON NULL INPUT;", "CREATE FUNCTION add ( integer, integer ) RETURNS integer LANGUAGE SQL IMMUTABLE RETURNS ? ON ? INPUT"),
3172 ("SELECT * FROM public.table ( array [ ROW ( array [ 'magic', 'foo',", "SELECT * FROM public.table ( array [ ROW ( array [ ?"),
3174 ("SELECT pg_try_advisory_lock (123) AS t46eef3f025cc27feb31ca5a2d668a09a", "SELECT pg_try_advisory_lock ( ? )"),
3176 ("INSERT INTO `qual-aa`.issues (alert0 , alert1) VALUES (NULL, NULL)", "INSERT INTO qual-aa . issues ( alert0, alert1 ) VALUES ( ? )"),
3178 ("INSERT INTO user (id, email, name) VALUES (null, ?, ?)", "INSERT INTO user ( id, email, name ) VALUES ( ? )"),
3180 ("select * from users where id = 214325346 # This comment continues to the end of line", "select * from users where id = ?"),
3182 ("select * from users where id = 214325346 -- This comment continues to the end of line", "select * from users where id = ?"),
3184 ("SELECT * FROM /* this is an in-line comment */ users;", "SELECT * FROM users"),
3186 ("SELECT /*! STRAIGHT_JOIN */ col1 FROM table1", "SELECT col1 FROM table1"),
3188 ("DELETE FROM t1\n\t\t\tWHERE s11 > ANY\n\t\t\t(SELECT COUNT(*) /* no hint */ FROM t2\n\t\t\tWHERE NOT EXISTS\n\t\t\t(SELECT * FROM t3\n\t\t\tWHERE ROW(5*t2.s1,77)=\n\t\t\t(SELECT 50,11*s1 FROM t4 UNION SELECT 50,77 FROM\n\t\t\t(SELECT * FROM t5) AS t5)));", "DELETE FROM t1 WHERE s11 > ANY ( SELECT COUNT ( * ) FROM t2 WHERE NOT EXISTS ( SELECT * FROM t3 WHERE ROW ( ? * t2.s1, ? ) = ( SELECT ? * s1 FROM t4 UNION SELECT ? FROM ( SELECT * FROM t5 ) ) ) )"),
3190 ("SET @g = 'POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))';", "SET @g = ?"),
3192 ("SELECT daily_values.*,\n LEAST((5040000 - @runtot), value) AS value,\n (@runtot := @runtot + daily_values.value) AS total FROM (SELECT @runtot:=0) AS n, `daily_values` WHERE `daily_values`.`subject_id` = 12345 AND `daily_values`.`subject_type` = 'Skippity' AND (daily_values.date BETWEEN '2018-05-09' AND '2018-06-19') HAVING value >= 0 ORDER BY date", "SELECT daily_values.*, LEAST ( ( ? - @runtot ), value ), ( @runtot := @runtot + daily_values.value ) FROM ( SELECT @runtot := ? ), daily_values WHERE daily_values . subject_id = ? AND daily_values . subject_type = ? AND ( daily_values.date BETWEEN ? AND ? ) HAVING value >= ? ORDER BY date"),
3194 (" SELECT\n t1.userid,\n t1.fullname,\n t1.firm_id,\n t2.firmname,\n t1.email,\n t1.location,\n t1.state,\n t1.phone,\n t1.url,\n DATE_FORMAT( t1.lastmod, \"%m/%d/%Y %h:%i:%s\" ) AS lastmod,\n t1.lastmod AS lastmod_raw,\n t1.user_status,\n t1.pw_expire,\n DATE_FORMAT( t1.pw_expire, \"%m/%d/%Y\" ) AS pw_expire_date,\n t1.addr1,\n t1.addr2,\n t1.zipcode,\n t1.office_id,\n t1.default_group,\n t3.firm_status,\n t1.title\n FROM\n userdata AS t1\n LEFT JOIN lawfirm_names AS t2 ON t1.firm_id = t2.firm_id\n LEFT JOIN lawfirms AS t3 ON t1.firm_id = t3.firm_id\n WHERE\n t1.userid = 'jstein'\n\n ", "SELECT t1.userid, t1.fullname, t1.firm_id, t2.firmname, t1.email, t1.location, t1.state, t1.phone, t1.url, DATE_FORMAT ( t1.lastmod, %m/%d/%Y %h:%i:%s ), t1.lastmod, t1.user_status, t1.pw_expire, DATE_FORMAT ( t1.pw_expire, %m/%d/%Y ), t1.addr1, t1.addr2, t1.zipcode, t1.office_id, t1.default_group, t3.firm_status, t1.title FROM userdata LEFT JOIN lawfirm_names ON t1.firm_id = t2.firm_id LEFT JOIN lawfirms ON t1.firm_id = t3.firm_id WHERE t1.userid = ?"),
3196 ("SELECT [b].[BlogId], [b].[Name]\nFROM [Blogs] AS [b]\nORDER BY [b].[Name]", "SELECT [ b ] . [ BlogId ], [ b ] . [ Name ] FROM [ Blogs ] ORDER BY [ b ] . [ Name ]"),
3198 ("SELECT * FROM users WHERE firstname=''", "SELECT * FROM users WHERE firstname = ?"),
3200 ("SELECT * FROM users WHERE firstname=' '", "SELECT * FROM users WHERE firstname = ?"),
3202 ("SELECT * FROM users WHERE firstname=\"\"", "SELECT * FROM users WHERE firstname = ?"),
3204 ("SELECT * FROM users WHERE lastname=\" \"", "SELECT * FROM users WHERE lastname = ?"),
3206 ("SELECT * FROM users WHERE lastname=\"\t \"", "SELECT * FROM users WHERE lastname = ?"),
3208 ("SELECT customer_item_list_id, customer_id FROM customer_item_list WHERE type = wishlist AND customer_id = ? AND visitor_id IS ? UNION SELECT customer_item_list_id, customer_id FROM customer_item_list WHERE type = wishlist AND customer_id IS ? AND visitor_id = \"AA0DKTGEM6LRN3WWPZ01Q61E3J7ROX7O\" ORDER BY customer_id DESC", "SELECT customer_item_list_id, customer_id FROM customer_item_list WHERE type = wishlist AND customer_id = ? AND visitor_id IS ? UNION SELECT customer_item_list_id, customer_id FROM customer_item_list WHERE type = wishlist AND customer_id IS ? AND visitor_id = ? ORDER BY customer_id DESC"),
3210 ("update Orders set created = \"2019-05-24 00:26:17\", gross = 30.28, payment_type = \"eventbrite\", mg_fee = \"3.28\", fee_collected = \"3.28\", event = 59366262, status = \"10\", survey_type = 'direct', tx_time_limit = 480, invite = \"\", ip_address = \"69.215.148.82\", currency = 'USD', gross_USD = \"30.28\", tax_USD = 0.00, journal_activity_id = 4044659812798558774, eb_tax = 0.00, eb_tax_USD = 0.00, cart_uuid = \"160b450e7df511e9810e0a0c06de92f8\", changed = '2019-05-24 00:26:17' where id = ?", "update Orders set created = ? gross = ? payment_type = ? mg_fee = ? fee_collected = ? event = ? status = ? survey_type = ? tx_time_limit = ? invite = ? ip_address = ? currency = ? gross_USD = ? tax_USD = ? journal_activity_id = ? eb_tax = ? eb_tax_USD = ? cart_uuid = ? changed = ? where id = ?"),
3212 ("update Attendees set email = '626837270@qq.com', first_name = \"贺新春送猪福加企鹅1054948000领98綵斟\", last_name = '王子198442com体验猪多优惠', journal_activity_id = 4246684839261125564, changed = \"2019-05-24 00:26:22\" where id = 123", "update Attendees set email = ? first_name = ? last_name = ? journal_activity_id = ? changed = ? where id = ?"),
3214 ("SELECT\r\n\t CodiFormacio\r\n\t ,DataInici\r\n\t ,DataFi\r\n\t ,Tipo\r\n\t ,CodiTecnicFormador\r\n\t ,p.nombre AS TutorNombre\r\n\t ,p.mail AS TutorMail\r\n\t ,Sessions.Direccio\r\n\t ,Sessions.NomEmpresa\r\n\t ,Sessions.Telefon\r\n FROM\r\n ----------------------------\r\n (SELECT\r\n\t CodiFormacio\r\n\t ,case\r\n\t when ModalitatSessio = '1' then 'Presencial'--Teoria\r\n\t when ModalitatSessio = '2' then 'Presencial'--Practica\r\n\t when ModalitatSessio = '3' then 'Online'--Tutoria\r\n when ModalitatSessio = '4' then 'Presencial'--Examen\r\n\t ELSE 'Presencial'\r\n\t end as Tipo\r\n\t ,ModalitatSessio\r\n\t ,DataInici\r\n\t ,DataFi\r\n ,NomEmpresa\r\n\t ,Telefon\r\n\t ,CodiTecnicFormador\r\n\t ,CASE\r\n\t WHEn EsAltres = 1 then FormacioLlocImparticioDescripcio\r\n\t else Adreca + ' - ' + CodiPostal + ' ' + Poblacio\r\n\t end as Direccio\r\n\t\r\n FROM Consultas.dbo.View_AsActiva__FormacioSessions_InfoLlocImparticio) AS Sessions\r\n ----------------------------------------\r\n LEFT JOIN Consultas.dbo.View_AsActiva_Operari AS o\r\n\t ON o.CodiOperari = Sessions.CodiTecnicFormador\r\n LEFT JOIN MainAPP.dbo.persona AS p\r\n\t ON 'preven\\' + o.codioperari = p.codi\r\n WHERE Sessions.CodiFormacio = 'F00000017898'", "SELECT CodiFormacio, DataInici, DataFi, Tipo, CodiTecnicFormador, p.nombre, p.mail, Sessions.Direccio, Sessions.NomEmpresa, Sessions.Telefon FROM ( SELECT CodiFormacio, case when ModalitatSessio = ? then ? when ModalitatSessio = ? then ? when ModalitatSessio = ? then ? when ModalitatSessio = ? then ? ELSE ? end, ModalitatSessio, DataInici, DataFi, NomEmpresa, Telefon, CodiTecnicFormador, CASE WHEn EsAltres = ? then FormacioLlocImparticioDescripcio else Adreca + ? + CodiPostal + ? + Poblacio end FROM Consultas.dbo.View_AsActiva__FormacioSessions_InfoLlocImparticio ) LEFT JOIN Consultas.dbo.View_AsActiva_Operari ON o.CodiOperari = Sessions.CodiTecnicFormador LEFT JOIN MainAPP.dbo.persona ON ? + o.codioperari = p.codi WHERE Sessions.CodiFormacio = ?"),
3216 ("SELECT * FROM foo LEFT JOIN bar ON 'backslash\\' = foo.b WHERE foo.name = 'String'", "SELECT * FROM foo LEFT JOIN bar ON ? = foo.b WHERE foo.name = ?"),
3218 ("SELECT * FROM foo LEFT JOIN bar ON 'backslash\\' = foo.b LEFT JOIN bar2 ON 'backslash2\\' = foo.b2 WHERE foo.name = 'String'", "SELECT * FROM foo LEFT JOIN bar ON ? = foo.b LEFT JOIN bar2 ON ? = foo.b2 WHERE foo.name = ?"),
3220 ("SELECT * FROM foo LEFT JOIN bar ON 'embedded ''quote'' in string' = foo.b WHERE foo.name = 'String'", "SELECT * FROM foo LEFT JOIN bar ON ? = foo.b WHERE foo.name = ?"),
3222 ("SELECT * FROM foo LEFT JOIN bar ON 'embedded \\'quote\\' in string' = foo.b WHERE foo.name = 'String'", "SELECT * FROM foo LEFT JOIN bar ON ? = foo.b WHERE foo.name = ?"),
3224 ("SELECT org_id,metric_key,metric_type,interval FROM metrics_metadata WHERE org_id = ? AND metric_key = ANY(ARRAY[?,?,?,?,?])", "SELECT org_id, metric_key, metric_type, interval FROM metrics_metadata WHERE org_id = ? AND metric_key = ANY ( ARRAY [ ? ] )"),
3226 ("SELECT wp_woocommerce_order_items.order_id As No_Commande\n\t\t\tFROM wp_woocommerce_order_items\n\t\t\tLEFT JOIN\n\t\t\t\t(\n\t\t\t\t\tSELECT meta_value As Prenom\n\t\t\t\t\tFROM wp_postmeta\n\t\t\t\t\tWHERE meta_key = '_shipping_first_name'\n\t\t\t\t) AS a\n\t\t\tON wp_woocommerce_order_items.order_id = a.post_id\n\t\t\tWHERE wp_woocommerce_order_items.order_id =2198", "SELECT wp_woocommerce_order_items.order_id FROM wp_woocommerce_order_items LEFT JOIN ( SELECT meta_value FROM wp_postmeta WHERE meta_key = ? ) ON wp_woocommerce_order_items.order_id = a.post_id WHERE wp_woocommerce_order_items.order_id = ?"),
3228 ("SELECT a :: VARCHAR(255) FROM foo WHERE foo.name = 'String'", "SELECT a :: VARCHAR ( ? ) FROM foo WHERE foo.name = ?"),
3230 ("SELECT MIN(`scoped_49a39c4cc9ae4fdda07bcf49e99f8224`.`scoped_8720d2c0e0824ec2910ab9479085839c`) AS `MIN_BECR_DATE_CREATED` FROM (SELECT `49a39c4cc9ae4fdda07bcf49e99f8224`.`submittedOn` AS `scoped_8720d2c0e0824ec2910ab9479085839c`, `49a39c4cc9ae4fdda07bcf49e99f8224`.`domain` AS `scoped_847e4dcfa1c54d72aad6dbeb231c46de`, `49a39c4cc9ae4fdda07bcf49e99f8224`.`eventConsumer` AS `scoped_7b2f7b8da15646d1b75aa03901460eb2`, `49a39c4cc9ae4fdda07bcf49e99f8224`.`eventType` AS `scoped_77a1b9308b384a9391b69d24335ba058` FROM (`SorDesignTime`.`businessEventConsumerRegistry_947a74dad4b64be9847d67f466d26f5e` AS `49a39c4cc9ae4fdda07bcf49e99f8224`) WHERE (`49a39c4cc9ae4fdda07bcf49e99f8224`.`systemData.ClientID`) = ('35c1ccc0-a83c-4812-a189-895e9d4dd223')) AS `scoped_49a39c4cc9ae4fdda07bcf49e99f8224` WHERE ((`scoped_49a39c4cc9ae4fdda07bcf49e99f8224`.`scoped_847e4dcfa1c54d72aad6dbeb231c46de`) = ('Benefits') AND ((`scoped_49a39c4cc9ae4fdda07bcf49e99f8224`.`scoped_7b2f7b8da15646d1b75aa03901460eb2`) = ('benefits') AND (`scoped_49a39c4cc9ae4fdda07bcf49e99f8224`.`scoped_77a1b9308b384a9391b69d24335ba058`) = ('DMXSync'))); ", "SELECT MIN ( scoped_49a39c4cc9ae4fdda07bcf49e99f8224 . scoped_8720d2c0e0824ec2910ab9479085839c ) FROM ( SELECT 49a39c4cc9ae4fdda07bcf49e99f8224 . submittedOn, 49a39c4cc9ae4fdda07bcf49e99f8224 . domain, 49a39c4cc9ae4fdda07bcf49e99f8224 . eventConsumer, 49a39c4cc9ae4fdda07bcf49e99f8224 . eventType FROM ( SorDesignTime . businessEventConsumerRegistry_947a74dad4b64be9847d67f466d26f5e ) WHERE ( 49a39c4cc9ae4fdda07bcf49e99f8224 . systemData.ClientID ) = ( ? ) ) WHERE ( ( scoped_49a39c4cc9ae4fdda07bcf49e99f8224 . scoped_847e4dcfa1c54d72aad6dbeb231c46de ) = ( ? ) AND ( ( scoped_49a39c4cc9ae4fdda07bcf49e99f8224 . scoped_7b2f7b8da15646d1b75aa03901460eb2 ) = ( ? ) AND ( scoped_49a39c4cc9ae4fdda07bcf49e99f8224 . scoped_77a1b9308b384a9391b69d24335ba058 ) = ( ? ) ) )"),
3232 ("{call px_cu_se_security_pg.sps_get_my_accounts_count(?, ?, ?, ?)}", "{ call px_cu_se_security_pg.sps_get_my_accounts_count ( ? ) }"),
3234 ("{call px_cu_se_security_pg.sps_get_my_accounts_count(1, 2, 'one', 'two')};", "{ call px_cu_se_security_pg.sps_get_my_accounts_count ( ? ) }"),
3236 ("{call curly_fun('{{', '}}', '}', '}')};", "{ call curly_fun ( ? ) }"),
3238 ("SELECT id, name FROM emp WHERE name LIKE {fn UCASE('Smith')}", "SELECT id, name FROM emp WHERE name LIKE ?"),
3240 ("select users.custom #- '{a,b}' from users", "select users.custom"),
3242 ("select users.custom #> '{a,b}' from users", "select users.custom"),
3244 ("select users.custom #>> '{a,b}' from users", "select users.custom"),
3246 ("SELECT a FROM foo WHERE value<@name", "SELECT a FROM foo WHERE value < @name"),
3248 ("SELECT @@foo", "SELECT @@foo"),
3250 ("DROP TABLE IF EXISTS django_site;\nDROP TABLE IF EXISTS knowledgebase_article;\n\nCREATE TABLE django_site (\n id integer PRIMARY KEY,\n domain character varying(100) NOT NULL,\n name character varying(50) NOT NULL,\n uuid uuid NOT NULL,\n disabled boolean DEFAULT false NOT NULL\n);\n\nCREATE TABLE knowledgebase_article (\n id integer PRIMARY KEY,\n title character varying(255) NOT NULL,\n site_id integer NOT NULL,\n CONSTRAINT knowledgebase_article_site_id_fkey FOREIGN KEY (site_id) REFERENCES django_site(id)\n);\n\nINSERT INTO django_site(id, domain, name, uuid, disabled) VALUES (1, 'foo.domain', 'Foo', 'cb4776c1-edf3-4041-96a8-e152f5ae0f91', false);\nINSERT INTO knowledgebase_article(id, title, site_id) VALUES(1, 'title', 1);", "DROP TABLE IF EXISTS django_site DROP TABLE IF EXISTS knowledgebase_article CREATE TABLE django_site ( id integer PRIMARY KEY, domain character varying ( ? ) NOT ? name character varying ( ? ) NOT ? uuid uuid NOT ? disabled boolean DEFAULT ? NOT ? ) CREATE TABLE knowledgebase_article ( id integer PRIMARY KEY, title character varying ( ? ) NOT ? site_id integer NOT ? CONSTRAINT knowledgebase_article_site_id_fkey FOREIGN KEY ( site_id ) REFERENCES django_site ( id ) ) INSERT INTO django_site ( id, domain, name, uuid, disabled ) VALUES ( ? ) INSERT INTO knowledgebase_article ( id, title, site_id ) VALUES ( ? )"),
3252 ("\nSELECT set_config('foo.bar', (SELECT foo.bar FROM sometable WHERE sometable.uuid = %(some_id)s)::text, FALSE);\nSELECT\n othertable.id,\n othertable.title\nFROM othertable\nINNER JOIN sometable ON sometable.id = othertable.site_id\nWHERE\n sometable.uuid = %(some_id)s\nLIMIT 1\n;", "SELECT set_config ( ? ( SELECT foo.bar FROM sometable WHERE sometable.uuid = ? ) :: text, ? ) SELECT othertable.id, othertable.title FROM othertable INNER JOIN sometable ON sometable.id = othertable.site_id WHERE sometable.uuid = ? LIMIT ?"),
3254 ("CREATE OR REPLACE FUNCTION pg_temp.sequelize_upsert(OUT created boolean, OUT primary_key text) AS $func$ BEGIN INSERT INTO \"school\" (\"id\",\"organization_id\",\"name\",\"created_at\",\"updated_at\") VALUES ('dc4e9444-d7c9-40a9-bcef-68e4cc594e61','ec647f56-f27a-49a1-84af-021ad0a19f21','Test','2021-03-31 16:30:43.915 +00:00','2021-03-31 16:30:43.915 +00:00'); created := true; EXCEPTION WHEN unique_violation THEN UPDATE \"school\" SET \"id\"='dc4e9444-d7c9-40a9-bcef-68e4cc594e61',\"organization_id\"='ec647f56-f27a-49a1-84af-021ad0a19f21',\"name\"='Test',\"updated_at\"='2021-03-31 16:30:43.915 +00:00' WHERE (\"id\" = 'dc4e9444-d7c9-40a9-bcef-68e4cc594e61'); created := false; END; $func$ LANGUAGE plpgsql; SELECT * FROM pg_temp.sequelize_upsert();", "CREATE OR REPLACE FUNCTION pg_temp.sequelize_upsert ( OUT created boolean, OUT primary_key text ) LANGUAGE plpgsql SELECT * FROM pg_temp.sequelize_upsert ( )"),
3256 ("INSERT INTO table (field1, field2) VALUES (1, $$someone's string123$with other things$$)", "INSERT INTO table ( field1, field2 ) VALUES ( ? )"),
3258 ("INSERT INTO table (field1) VALUES ($some tag$this text confuses$some other text$some ta not quite$some tag$)", "INSERT INTO table ( field1 ) VALUES ( ? )"),
3260 ("INSERT INTO table (field1) VALUES ($tag$random \\wqejks \"sadads' text$tag$)", "INSERT INTO table ( field1 ) VALUES ( ? )"),
3262 ("SELECT nspname FROM pg_class where nspname !~ '.*toIgnore.*'", "SELECT nspname FROM pg_class where nspname !~ ?"),
3264 ("SELECT nspname FROM pg_class where nspname !~* '.*toIgnoreInsensitive.*'", "SELECT nspname FROM pg_class where nspname !~* ?"),
3266 ("SELECT nspname FROM pg_class where nspname ~ '.*matching.*'", "SELECT nspname FROM pg_class where nspname ~ ?"),
3268 ("SELECT nspname FROM pg_class where nspname ~* '.*matchingInsensitive.*'", "SELECT nspname FROM pg_class where nspname ~* ?"),
3270 ("SELECT * FROM dbo.Items WHERE id = 1 or /*!obfuscation*/ 1 = 1", "SELECT * FROM dbo.Items WHERE id = ? or ? = ?"),
3272 ("SELECT * FROM Items WHERE id = -1 OR id = -01 OR id = -108 OR id = -.018 OR id = -.08 OR id = -908129", "SELECT * FROM Items WHERE id = ? OR id = ? OR id = ? OR id = ? OR id = ? OR id = ?"),
3274 ("USING $09 SELECT", "USING ? SELECT"),
3276 ("USING - SELECT", "USING - SELECT"),
3278 ("select key, status, modified from org_check_run where org_id = %s and check in (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", "select key, status, modified from org_check_run where org_id = ? and check in ( ? )"),
3280 ("select key, status, modified from org_check_run where org_id = %s and check in (%s, %s, %s)", "select key, status, modified from org_check_run where org_id = ? and check in ( ? )"),
3282 ("select key, status, modified from org_check_run where org_id = %s and check in (%s , %s , %s )", "select key, status, modified from org_check_run where org_id = ? and check in ( ? )"),
3284 ("select key, status, modified from org_check_run where org_id = %s and check = %s", "select key, status, modified from org_check_run where org_id = ? and check = ?"),
3286 ("SELECT timestamp, processes FROM process_snapshot.minutely WHERE org_id = ? AND host = ? AND timestamp >= ? AND timestamp <= ?", "SELECT timestamp, processes FROM process_snapshot.minutely WHERE org_id = ? AND host = ? AND timestamp >= ? AND timestamp <= ?"),
3288 ("SELECT count(*) AS totcount FROM (SELECT \"c1\", \"c2\",\"c3\",\"c4\",\"c5\",\"c6\",\"c7\",\"c8\", \"c9\", \"c10\",\"c11\",\"c12\",\"c13\",\"c14\", \"c15\",\"c16\",\"c17\",\"c18\", \"c19\",\"c20\",\"c21\",\"c22\",\"c23\", \"c24\",\"c25\",\"c26\", \"c27\" FROM (SELECT bar.y AS \"c2\", foo.x AS \"c3\", foo.z AS \"c4\", DECODE(foo.a, NULL,NULL, foo.a ||?|| foo.b) AS \"c5\" , foo.c AS \"c6\", bar.d AS \"c1\", bar.e AS \"c7\", bar.f AS \"c8\", bar.g AS \"c9\", TO_DATE(TO_CHAR(TO_DATE(bar.h,?),?),?) AS \"c10\", TO_DATE(TO_CHAR(TO_DATE(bar.i,?),?),?) AS \"c11\", CASE WHEN DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?))) > ? THEN ? WHEN DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?))) > ? THEN ? WHEN DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?))) > ? THEN ? WHEN DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?))) > ? THEN ? WHEN DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?))) > ? THEN ? WHEN DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?))) > ? THEN ? ELSE NULL END AS \"c12\", DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?)),NULL) as \"c13\", bar.k AS \"c14\", bar.l ||?||bar.m AS \"c15\", DECODE(bar.n, NULL, NULL,bar.n ||?||bar.o) AS \"c16\", bar.p AS \"c17\", bar.q AS \"c18\", bar.r AS \"c19\", bar.s AS \"c20\", qux.a AS \"c21\", TO_CHAR(TO_DATE(qux.b,?),?) AS \"c22\", DECODE(qux.l,NULL,NULL, qux.l ||?||qux.m) AS \"c23\", bar.a AS \"c24\", TO_CHAR(TO_DATE(bar.j,?),?) AS \"c25\", DECODE(bar.c , ?,?,?, ?, bar.c ) AS \"c26\", bar.y AS y, bar.d, bar.d AS \"c27\" FROM blort.bar , ( SELECT * FROM (SELECT a,a,l,m,b,c, RANK() OVER (PARTITION BY c ORDER BY b DESC) RNK FROM blort.d WHERE y IN (:p)) WHERE RNK = ?) qux, blort.foo WHERE bar.c = qux.c(+) AND bar.x = foo.x AND bar.y IN (:p) and bar.x IN (:x)) )\nSELECT count(*) AS totcount FROM (SELECT \"c1\", \"c2\",\"c3\",\"c4\",\"c5\",\"c6\",\"c7\",\"c8\", \"c9\", \"c10\",\"c11\",\"c12\",\"c13\",\"c14\", \"c15\",\"c16\",\"c17\",\"c18\", \"c19\",\"c20\",\"c21\",\"c22\",\"c23\", \"c24\",\"c25\",\"c26\", \"c27\" FROM (SELECT bar.y AS \"c2\", foo.x AS \"c3\", foo.z AS \"c4\", DECODE(foo.a, NULL,NULL, foo.a ||?|| foo.b) AS \"c5\" , foo.c AS \"c6\", bar.d AS \"c1\", bar.e AS \"c7\", bar.f AS \"c8\", bar.g AS \"c9\", TO_DATE(TO_CHAR(TO_DATE(bar.h,?),?),?) AS \"c10\", TO_DATE(TO_CHAR(TO_DATE(bar.i,?),?),?) AS \"c11\", CASE WHEN DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?))) > ? THEN ? WHEN DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?))) > ? THEN ? WHEN DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?))) > ? THEN ? WHEN DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?))) > ? THEN ? WHEN DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?))) > ? THEN ? WHEN DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?))) > ? THEN ? ELSE NULL END AS \"c12\", DECODE(bar.j, NULL, TRUNC(SYSDATE) - TRUNC(TO_DATE(bar.h,?)),NULL) as \"c13\", bar.k AS \"c14\", bar.l ||?||bar.m AS \"c15\", DECODE(bar.n, NULL, NULL,bar.n ||?||bar.o) AS \"c16\", bar.p AS \"c17\", bar.q AS \"c18\", bar.r AS \"c19\", bar.s AS \"c20\", qux.a AS \"c21\", TO_CHAR(TO_DATE(qux.b,?),?) AS \"c22\", DECODE(qux.l,NULL,NULL, qux.l ||?||qux.m) AS \"c23\", bar.a AS \"c24\", TO_CHAR(TO_DATE(bar.j,?),?) AS \"c25\", DECODE(bar.c , ?,?,?, ?, bar.c ) AS \"c26\", bar.y AS y, bar.d, bar.d AS \"c27\" FROM blort.bar , ( SELECT * FROM (SELECT a,a,l,m,b,c, RANK() OVER (PARTITION BY c ORDER BY b DESC) RNK FROM blort.d WHERE y IN (:p)) WHERE RNK = ?) qux, blort.foo WHERE bar.c = qux.c(+) AND bar.x = foo.x AND bar.y IN (:p) and bar.x IN (:x)) )", "SELECT count ( * ) FROM ( SELECT c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20, c21, c22, c23, c24, c25, c26, c27 FROM ( SELECT bar.y, foo.x, foo.z, DECODE ( foo.a, ? foo.a | | ? | | foo.b ), foo.c, bar.d, bar.e, bar.f, bar.g, TO_DATE ( TO_CHAR ( TO_DATE ( bar.h, ? ) ) ), TO_DATE ( TO_CHAR ( TO_DATE ( bar.i, ? ) ) ), CASE WHEN DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ) > ? THEN ? WHEN DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ) > ? THEN ? WHEN DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ) > ? THEN ? WHEN DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ) > ? THEN ? WHEN DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ) > ? THEN ? WHEN DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ) > ? THEN ? ELSE ? END, DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ), bar.k, bar.l | | ? | | bar.m, DECODE ( bar.n, ? bar.n | | ? | | bar.o ), bar.p, bar.q, bar.r, bar.s, qux.a, TO_CHAR ( TO_DATE ( qux.b, ? ) ), DECODE ( qux.l, ? qux.l | | ? | | qux.m ), bar.a, TO_CHAR ( TO_DATE ( bar.j, ? ) ), DECODE ( bar.c, ? bar.c ), bar.y, bar.d, bar.d FROM blort.bar, ( SELECT * FROM ( SELECT a, a, l, m, b, c, RANK ( ) OVER ( PARTITION BY c ORDER BY b DESC ) RNK FROM blort.d WHERE y IN ( :p ) ) WHERE RNK = ? ) qux, blort.foo WHERE bar.c = qux.c ( + ) AND bar.x = foo.x AND bar.y IN ( :p ) and bar.x IN ( :x ) ) ) SELECT count ( * ) FROM ( SELECT c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20, c21, c22, c23, c24, c25, c26, c27 FROM ( SELECT bar.y, foo.x, foo.z, DECODE ( foo.a, ? foo.a | | ? | | foo.b ), foo.c, bar.d, bar.e, bar.f, bar.g, TO_DATE ( TO_CHAR ( TO_DATE ( bar.h, ? ) ) ), TO_DATE ( TO_CHAR ( TO_DATE ( bar.i, ? ) ) ), CASE WHEN DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ) > ? THEN ? WHEN DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ) > ? THEN ? WHEN DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ) > ? THEN ? WHEN DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ) > ? THEN ? WHEN DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ) > ? THEN ? WHEN DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ) > ? THEN ? ELSE ? END, DECODE ( bar.j, ? TRUNC ( SYSDATE ) - TRUNC ( TO_DATE ( bar.h, ? ) ) ), bar.k, bar.l | | ? | | bar.m, DECODE ( bar.n, ? bar.n | | ? | | bar.o ), bar.p, bar.q, bar.r, bar.s, qux.a, TO_CHAR ( TO_DATE ( qux.b, ? ) ), DECODE ( qux.l, ? qux.l | | ? | | qux.m ), bar.a, TO_CHAR ( TO_DATE ( bar.j, ? ) ), DECODE ( bar.c, ? bar.c ), bar.y, bar.d, bar.d FROM blort.bar, ( SELECT * FROM ( SELECT a, a, l, m, b, c, RANK ( ) OVER ( PARTITION BY c ORDER BY b DESC ) RNK FROM blort.d WHERE y IN ( :p ) ) WHERE RNK = ? ) qux, blort.foo WHERE bar.c = qux.c ( + ) AND bar.x = foo.x AND bar.y IN ( :p ) and bar.x IN ( :x ) ) )"),
3290 ("1234", "?"),
3292 ("-1234", "?"),
3294 ("1234e12", "?"),
3296 ("0xfa", "?"),
3298 ("01234567", "?"),
3300 ("09", "?"),
3302 ("-01234567", "?"),
3304 ("-012345678", "?"),
3306 ];
3307
3308 #[test]
3309 fn test_sql_obfuscation_suite() {
3310 let mut errors = String::new();
3311 for (i, (input, expected)) in SUITE_CASES.iter().enumerate() {
3312 let got = super::obfuscate_sql_string(input);
3313 if got != *expected {
3314 errors.push_str(&format!(
3315 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3316 ));
3317 }
3318 }
3319 if !errors.is_empty() {
3320 panic!("{errors}");
3321 }
3322 }
3323
3324 #[test]
3326 #[allow(deprecated)]
3327 fn test_suite_keep_sql_alias() {
3328 let config = SqlObfuscateConfig {
3329 keep_sql_alias: true,
3330 ..Default::default()
3331 };
3332 let cases: &[(&str, &str)] = &[
3333 (
3335 "SELECT username AS person FROM users WHERE id=4",
3336 "SELECT username AS person FROM users WHERE id = ?",
3337 ),
3338 ];
3339 let mut errors = String::new();
3340 for (i, (input, expected)) in cases.iter().enumerate() {
3341 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3342 if got != *expected {
3343 errors.push_str(&format!(
3344 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3345 ));
3346 }
3347 }
3348 if !errors.is_empty() {
3349 panic!("{errors}");
3350 }
3351 }
3352
3353 #[test]
3355 #[allow(deprecated)]
3356 fn test_suite_dollar_quoted_func() {
3357 let config = SqlObfuscateConfig {
3358 dollar_quoted_func: true,
3359 ..Default::default()
3360 };
3361 let cases: &[(&str, &str)] = &[
3362 (
3364 "SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users",
3365 "SELECT $func$INSERT INTO table VALUES ( ? )$func$ FROM users",
3366 ),
3367 ];
3368 let mut errors = String::new();
3369 for (i, (input, expected)) in cases.iter().enumerate() {
3370 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3371 if got != *expected {
3372 errors.push_str(&format!(
3373 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3374 ));
3375 }
3376 }
3377 if !errors.is_empty() {
3378 panic!("{errors}");
3379 }
3380 }
3381
3382 #[test]
3384 #[allow(deprecated)]
3385 fn test_suite_keep_sql_alias_dollar_quoted_func() {
3386 let config = SqlObfuscateConfig {
3387 keep_sql_alias: true,
3388 dollar_quoted_func: true,
3389 ..Default::default()
3390 };
3391 let cases: &[(&str, &str)] = &[
3392 ("CREATE OR REPLACE FUNCTION pg_temp.sequelize_upsert(OUT created boolean, OUT primary_key text) AS $func$ BEGIN INSERT INTO \"school\" (\"id\",\"organization_id\",\"name\",\"created_at\",\"updated_at\") VALUES ('dc4e9444-d7c9-40a9-bcef-68e4cc594e61','ec647f56-f27a-49a1-84af-021ad0a19f21','Test','2021-03-31 16:30:43.915 +00:00','2021-03-31 16:30:43.915 +00:00'); created := true; EXCEPTION WHEN unique_violation THEN UPDATE \"school\" SET \"id\"='dc4e9444-d7c9-40a9-bcef-68e4cc594e61',\"organization_id\"='ec647f56-f27a-49a1-84af-021ad0a19f21',\"name\"='Test',\"updated_at\"='2021-03-31 16:30:43.915 +00:00' WHERE (\"id\" = 'dc4e9444-d7c9-40a9-bcef-68e4cc594e61'); created := false; END; $func$ LANGUAGE plpgsql; SELECT * FROM pg_temp.sequelize_upsert();", "CREATE OR REPLACE FUNCTION pg_temp.sequelize_upsert ( OUT created boolean, OUT primary_key text ) AS $func$BEGIN INSERT INTO school ( id, organization_id, name, created_at, updated_at ) VALUES ( ? ) created := ? EXCEPTION WHEN unique_violation THEN UPDATE school SET id = ? organization_id = ? name = ? updated_at = ? WHERE ( id = ? ) created := ? END$func$ LANGUAGE plpgsql SELECT * FROM pg_temp.sequelize_upsert ( )"),
3394 ];
3395 let mut errors = String::new();
3396 for (i, (input, expected)) in cases.iter().enumerate() {
3397 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3398 if got != *expected {
3399 errors.push_str(&format!(
3400 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3401 ));
3402 }
3403 }
3404 if !errors.is_empty() {
3405 panic!("{errors}");
3406 }
3407 }
3408
3409 #[test]
3411 #[allow(deprecated)]
3412 fn test_suite_replace_digits() {
3413 let config = SqlObfuscateConfig {
3414 replace_digits: true,
3415 ..Default::default()
3416 };
3417 let cases: &[(&str, &str)] = &[
3418 ("\n/* Multi-line comment\nwith line breaks */\nWITH sales AS\n(SELECT sf2.*\n\tFROM gosalesdw28391.sls_order_method_dim AS md,\n\t\tgosalesdw1920.sls_product_dim391 AS pd190,\n\t\tgosalesdw3819.emp_employee_dim AS ed,\n\t\tgosalesdw3919.sls_sales_fact3819 AS sf2\n\tWHERE pd190.product_key = sf2.product_key\n\tAND pd190.product_number381 > 10000\n\tAND pd190.base_product_key > 30\n\tAND md.order_method_key = sf2.order_method_key8319\n\tAND md.order_method_code > 5\n\tAND ed.employee_key = sf2.employee_key\n\tAND ed.manager_code1 > 20),\ninventory3118 AS\n(SELECT if.*\n\tFROM gosalesdw1592.go_branch_dim AS bd3221,\n\tgosalesdw.dist_inventory_fact AS if\n\tWHERE if.branch_key = bd3221.branch_key\n\tAND bd3221.branch_code > 20)\nSELECT sales1828.product_key AS PROD_KEY,\nSUM(CAST (inventory3118.quantity_shipped AS BIGINT)) AS INV_SHIPPED3118,\nSUM(CAST (sales1828.quantity AS BIGINT)) AS PROD_QUANTITY,\nRANK() OVER ( ORDER BY SUM(CAST (sales1828.quantity AS BIGINT)) DESC) AS PROD_RANK\nFROM sales1828, inventory3118\nWHERE sales1828.product_key = inventory3118.product_key\nGROUP BY sales1828.product_key", "WITH sales SELECT sf?.* FROM gosalesdw?.sls_order_method_dim, gosalesdw?.sls_product_dim?, gosalesdw?.emp_employee_dim, gosalesdw?.sls_sales_fact? WHERE pd?.product_key = sf?.product_key AND pd?.product_number? > ? AND pd?.base_product_key > ? AND md.order_method_key = sf?.order_method_key? AND md.order_method_code > ? AND ed.employee_key = sf?.employee_key AND ed.manager_code? > ? ) inventory? SELECT if.* FROM gosalesdw?.go_branch_dim, gosalesdw.dist_inventory_fact WHERE if.branch_key = bd?.branch_key AND bd?.branch_code > ? ) SELECT sales?.product_key, SUM ( CAST ( inventory?.quantity_shipped ) ), SUM ( CAST ( sales?.quantity ) ), RANK ( ) OVER ( ORDER BY SUM ( CAST ( sales?.quantity ) ) DESC ) FROM sales?, inventory? WHERE sales?.product_key = inventory?.product_key GROUP BY sales?.product_key"),
3420 ("REPLACE INTO sales_2019_07_01 (`itemID`, `date`, `qty`, `price`) VALUES ((SELECT itemID FROM item1001 WHERE `sku` = [sku]), CURDATE(), [qty], 0.00)", "REPLACE INTO sales_?_?_? ( itemID, date, qty, price ) VALUES ( ( SELECT itemID FROM item? WHERE sku = [ sku ] ), CURDATE ( ), [ qty ], ? )"),
3422 ("SELECT ddh19.name, ddt.tags FROM dd91219.host ddh19, dd21916.host_tags ddt WHERE ddh19.id = ddt.host_id AND ddh19.org_id = 2 AND ddh19.name = 'datadog'", "SELECT ddh?.name, ddt.tags FROM dd?.host ddh?, dd?.host_tags ddt WHERE ddh?.id = ddt.host_id AND ddh?.org_id = ? AND ddh?.name = ?"),
3424 ("SELECT ddu2.name, ddo.id10, ddk.app_key52 FROM dd3120.user ddu2, dd1931.orgs55 ddo, dd53819.keys ddk", "SELECT ddu?.name, ddo.id?, ddk.app_key? FROM dd?.user ddu?, dd?.orgs? ddo, dd?.keys ddk"),
3426 ("SELECT daily_values1529.*, LEAST((5040000 - @runtot), value1830) AS value1830,\n(@runtot := @runtot + daily_values1529.value1830) AS total\nFROM (SELECT @runtot:=0) AS n,\ndaily_values1529 WHERE daily_values1529.subject_id = 12345 AND daily_values1592.subject_type = 'Skippity'\nAND (daily_values1529.date BETWEEN '2018-05-09' AND '2018-06-19') HAVING value >= 0 ORDER BY date", "SELECT daily_values?.*, LEAST ( ( ? - @runtot ), value? ), ( @runtot := @runtot + daily_values?.value? ) FROM ( SELECT @runtot := ? ), daily_values? WHERE daily_values?.subject_id = ? AND daily_values?.subject_type = ? AND ( daily_values?.date BETWEEN ? AND ? ) HAVING value >= ? ORDER BY date"),
3428 ("WITH\nsales AS\n(SELECT sf2.*\n\tFROM gosalesdw28391.sls_order_method_dim AS md,\n\t\tgosalesdw1920.sls_product_dim391 AS pd190,\n\t\tgosalesdw3819.emp_employee_dim AS ed,\n\t\tgosalesdw3919.sls_sales_fact3819 AS sf2\n\tWHERE pd190.product_key = sf2.product_key\n\tAND pd190.product_number381 > 10000\n\tAND pd190.base_product_key > 30\n\tAND md.order_method_key = sf2.order_method_key8319\n\tAND md.order_method_code > 5\n\tAND ed.employee_key = sf2.employee_key\n\tAND ed.manager_code1 > 20),\ninventory3118 AS\n(SELECT if.*\n\tFROM gosalesdw1592.go_branch_dim AS bd3221,\n\tgosalesdw.dist_inventory_fact AS if\n\tWHERE if.branch_key = bd3221.branch_key\n\tAND bd3221.branch_code > 20)\nSELECT sales1828.product_key AS PROD_KEY,\nSUM(CAST (inventory3118.quantity_shipped AS BIGINT)) AS INV_SHIPPED3118,\nSUM(CAST (sales1828.quantity AS BIGINT)) AS PROD_QUANTITY,\nRANK() OVER ( ORDER BY SUM(CAST (sales1828.quantity AS BIGINT)) DESC) AS PROD_RANK\nFROM sales1828, inventory3118\nWHERE sales1828.product_key = inventory3118.product_key\nGROUP BY sales1828.product_key", "WITH sales SELECT sf?.* FROM gosalesdw?.sls_order_method_dim, gosalesdw?.sls_product_dim?, gosalesdw?.emp_employee_dim, gosalesdw?.sls_sales_fact? WHERE pd?.product_key = sf?.product_key AND pd?.product_number? > ? AND pd?.base_product_key > ? AND md.order_method_key = sf?.order_method_key? AND md.order_method_code > ? AND ed.employee_key = sf?.employee_key AND ed.manager_code? > ? ) inventory? SELECT if.* FROM gosalesdw?.go_branch_dim, gosalesdw.dist_inventory_fact WHERE if.branch_key = bd?.branch_key AND bd?.branch_code > ? ) SELECT sales?.product_key, SUM ( CAST ( inventory?.quantity_shipped ) ), SUM ( CAST ( sales?.quantity ) ), RANK ( ) OVER ( ORDER BY SUM ( CAST ( sales?.quantity ) ) DESC ) FROM sales?, inventory? WHERE sales?.product_key = inventory?.product_key GROUP BY sales?.product_key"),
3430 ("select * from users where id = 42", "select * from users where id = ?"),
3432 ("select * from `backslashes` where id = 42", "select * from backslashes where id = ?"),
3434 ("select * from \"double-quotes\" where id = 42", "select * from double-quotes where id = ?"),
3436 ("SELECT host, status FROM ec2_status WHERE org_id = 42", "SELECT host, status FROM ec?_status WHERE org_id = ?"),
3438 ("SELECT * FROM (SELECT * FROM nested_table)", "SELECT * FROM ( SELECT * FROM nested_table )"),
3440 (" -- get user \n--\n select * \n from users \n where\n id = 214325346 ", "select * from users where id = ?"),
3442 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 1, 20", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3444 ("UPDATE user_dash_pref SET json_prefs = %(json_prefs)s, modified = '2015-08-27 22:10:32.492912' WHERE user_id = %(user_id)s AND url = %(url)s", "UPDATE user_dash_pref SET json_prefs = ? modified = ? WHERE user_id = ? AND url = ?"),
3446 ("SELECT DISTINCT host.id AS host_id FROM host JOIN host_alias ON host_alias.host_id = host.id WHERE host.org_id = %(org_id_1)s AND host.name NOT IN (%(name_1)s) AND host.name IN (%(name_2)s, %(name_3)s, %(name_4)s, %(name_5)s)", "SELECT DISTINCT host.id FROM host JOIN host_alias ON host_alias.host_id = host.id WHERE host.org_id = ? AND host.name NOT IN ( ? ) AND host.name IN ( ? )"),
3448 ("update Orders set created = \"2019-05-24 00:26:17\", gross = 30.28, payment_type = \"eventbrite\", mg_fee = \"3.28\", fee_collected = \"3.28\", event = 59366262, status = \"10\", survey_type = 'direct', tx_time_limit = 480, invite = \"\", ip_address = \"69.215.148.82\", currency = 'USD', gross_USD = \"30.28\", tax_USD = 0.00, journal_activity_id = 4044659812798558774, eb_tax = 0.00, eb_tax_USD = 0.00, cart_uuid = \"160b450e7df511e9810e0a0c06de92f8\", changed = '2019-05-24 00:26:17' where id = ?", "update Orders set created = ? gross = ? payment_type = ? mg_fee = ? fee_collected = ? event = ? status = ? survey_type = ? tx_time_limit = ? invite = ? ip_address = ? currency = ? gross_USD = ? tax_USD = ? journal_activity_id = ? eb_tax = ? eb_tax_USD = ? cart_uuid = ? changed = ? where id = ?"),
3450 ("SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1 BEGIN INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 1, NULL, '2011-08-30 05:22:57') COMMIT", "SELECT * FROM clients WHERE ( clients.first_name = ? ) LIMIT ? BEGIN INSERT INTO clients ( created_at, first_name, locked, orders_count, updated_at ) VALUES ( ? ) COMMIT"),
3452 ("DELETE FROM table WHERE table.a=1", "DELETE FROM table WHERE table.a = ?"),
3454 ("SELECT wp_woocommerce_order_items.order_id FROM wp_woocommerce_order_items LEFT JOIN ( SELECT meta_value FROM wp_postmeta WHERE meta_key = ? ) ON wp_woocommerce_order_items.order_id = a.post_id WHERE wp_woocommerce_order_items.order_id = ?", "SELECT wp_woocommerce_order_items.order_id FROM wp_woocommerce_order_items LEFT JOIN ( SELECT meta_value FROM wp_postmeta WHERE meta_key = ? ) ON wp_woocommerce_order_items.order_id = a.post_id WHERE wp_woocommerce_order_items.order_id = ?"),
3456 ("REPLACE INTO sales_2019_07_01 (`itemID`, `date`, `qty`, `price`) VALUES ((SELECT itemID FROM item1001 WHERE `sku` = [sku]), CURDATE(), [qty], 0.00)", "REPLACE INTO sales_?_?_? ( itemID, date, qty, price ) VALUES ( ( SELECT itemID FROM item? WHERE sku = [ sku ] ), CURDATE ( ), [ qty ], ? )"),
3458 ("SELECT name FROM people WHERE person_id = -1", "SELECT name FROM people WHERE person_id = ?"),
3460 ("select * from test where !is_good;", "select * from test where ! is_good"),
3462 ("select * from test where ! is_good;", "select * from test where ! is_good"),
3464 ("select * from test where !45;", "select * from test where ! ?"),
3466 ("select * from test where !(select is_good from good_things);", "select * from test where ! ( select is_good from good_things )"),
3468 ("select * from test where !'weird_query'", "select * from test where ! ?"),
3470 ("select * from test where !\"weird_query\"", "select * from test where ! weird_query"),
3472 ("select * from test where !`weird_query`", "select * from test where ! weird_query"),
3474 ("select !- 2", "select ! - ?"),
3476 ("select !+2", "select ! + ?"),
3478 ("select * from test where !- 2", "select * from test where ! - ?"),
3480 ("select count(*) as `count(*)` from test", "select count ( * ) from test"),
3482 ("SELECT age as `age}` FROM profile", "SELECT age FROM profile"),
3484 ("SELECT age as `age``}` FROM profile", "SELECT age FROM profile"),
3486 ("SELECT * from users where user_id =:0_USER", "SELECT * from users where user_id = :0_USER"),
3488 ];
3489 let mut errors = String::new();
3490 for (i, (input, expected)) in cases.iter().enumerate() {
3491 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3492 if got != *expected {
3493 errors.push_str(&format!(
3494 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3495 ));
3496 }
3497 }
3498 if !errors.is_empty() {
3499 panic!("{errors}");
3500 }
3501 }
3502
3503 #[test]
3508 #[allow(deprecated)]
3509 fn test_suite_all_flags() {
3510 let config = SqlObfuscateConfig {
3511 keep_sql_alias: true,
3512 dollar_quoted_func: true,
3513 keep_null: true,
3514 keep_boolean: true,
3515 keep_positional_parameter: true,
3516 keep_trailing_semicolon: true,
3517 keep_identifier_quotation: true,
3518 replace_bind_parameter: true,
3519 remove_space_between_parentheses: true,
3520 keep_json_path: true,
3521 replace_digits: true,
3522 ..Default::default()
3523 };
3524 let cases: &[(&str, &str)] = &[
3525 ("$2", "?"),
3527 ("(2", "( ?"),
3529 (";ჸ", "ჸ"),
3531 (";ჸ", "ჸ"),
3533 ("@2", "@?"),
3535 ("@C", "@C"),
3537 ("\"\"\"\"", "\""),
3539 ("@ჸ2", "@ჸ?"),
3541 ("\"0\"", "0"),
3543 ];
3544 let mut errors = String::new();
3545 for (i, (input, expected)) in cases.iter().enumerate() {
3546 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3547 if got != *expected {
3548 errors.push_str(&format!(
3549 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3550 ));
3551 }
3552 }
3553 if !errors.is_empty() {
3554 panic!("{errors}");
3555 }
3556 }
3557
3558 #[test]
3560 #[allow(deprecated)]
3561 fn test_suite_mssql() {
3562 let config = SqlObfuscateConfig::default();
3563 let cases: &[(&str, &str)] = &[
3564 ("\n\tMERGE INTO Employees AS target\n\tUSING EmployeeUpdates AS source\n\tON (target.EmployeeID = source.EmployeeID)\n\tWHEN MATCHED THEN\n\t\tUPDATE SET\n\t\t\ttarget.Name = source.Name\n\tWHEN NOT MATCHED BY TARGET THEN\n\t\tINSERT (EmployeeID, Name)\n\t\tVALUES (source.EmployeeID, source.Name)\n\tWHEN NOT MATCHED BY SOURCE THEN\n\t\tDELETE\n\tOUTPUT $action, inserted.*, deleted.*;\n\t", "MERGE INTO Employees USING EmployeeUpdates ON ( target.EmployeeID = source.EmployeeID ) WHEN MATCHED THEN UPDATE SET target.Name = source.Name WHEN NOT MATCHED BY TARGET THEN INSERT ( EmployeeID, Name ) VALUES ( source.EmployeeID, source.Name ) WHEN NOT MATCHED BY SOURCE THEN DELETE OUTPUT $action, inserted.*, deleted.*"),
3566 ("select * from ##ThisIsAGlobalTempTable where id = 1", "select * from ##ThisIsAGlobalTempTable where id = ?"),
3568 ("select * from dbo.#ThisIsATempTable where id = 1", "select * from dbo.#ThisIsATempTable where id = ?"),
3570 ("SELECT * from [db_users] where [id] = @1", "SELECT * from db_users where id = @1"),
3572 ];
3573 let mut errors = String::new();
3574 for (i, (input, expected)) in cases.iter().enumerate() {
3575 let got = super::obfuscate_sql(input, &config, DbmsKind::Mssql);
3576 if got != *expected {
3577 errors.push_str(&format!(
3578 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3579 ));
3580 }
3581 }
3582 if !errors.is_empty() {
3583 panic!("{errors}");
3584 }
3585 }
3586
3587 #[test]
3589 #[allow(deprecated)]
3590 fn test_suite_postgresql() {
3591 let config = SqlObfuscateConfig::default();
3592 let cases: &[(&str, &str)] = &[
3593 (
3595 "select users.custom #> '{a,b}' from users",
3596 "select users.custom #> ? from users",
3597 ),
3598 (
3600 "select users.custom #>> '{a,b}' from users",
3601 "select users.custom #>> ? from users",
3602 ),
3603 (
3605 "select users.custom #- '{a,b}' from users",
3606 "select users.custom #- ? from users",
3607 ),
3608 (
3610 "select users.custom -> 'foo' from users",
3611 "select users.custom -> ? from users",
3612 ),
3613 (
3615 "select users.custom ->> 'foo' from users",
3616 "select users.custom ->> ? from users",
3617 ),
3618 (
3620 "select * from users where user.custom @> '{a,b}'",
3621 "select * from users where user.custom @> ?",
3622 ),
3623 (
3625 "SELECT a FROM foo WHERE value<@name",
3626 "SELECT a FROM foo WHERE value <@ name",
3627 ),
3628 (
3630 "select * from users where user.custom ? 'foo'",
3631 "select * from users where user.custom ? ?",
3632 ),
3633 (
3635 "select * from users where user.custom ?| array [ '1', '2' ]",
3636 "select * from users where user.custom ?| array [ ? ]",
3637 ),
3638 (
3640 "select * from users where user.custom ?& array [ '1', '2' ]",
3641 "select * from users where user.custom ?& array [ ? ]",
3642 ),
3643 ];
3644 let mut errors = String::new();
3645 for (i, (input, expected)) in cases.iter().enumerate() {
3646 let got = super::obfuscate_sql(input, &config, DbmsKind::Postgresql);
3647 if got != *expected {
3648 errors.push_str(&format!(
3649 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3650 ));
3651 }
3652 }
3653 if !errors.is_empty() {
3654 panic!("{errors}");
3655 }
3656 }
3657
3658 #[test]
3660 fn test_suite_normalize_only() {
3661 let config = SqlObfuscateConfig {
3662 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
3663 ..Default::default()
3664 };
3665 let cases: &[(&str, &str)] = &[
3666 ("SELECT * FROM users WHERE id = 1", "SELECT * FROM users WHERE id = 1"),
3668 ("SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users", "SELECT $func$INSERT INTO table VALUES ( 'a', 1, 2 )$func$ FROM users"),
3670 ("CREATE PROCEDURE TestProc AS BEGIN SELECT * FROM users WHERE id = 1 END", "CREATE PROCEDURE TestProc AS BEGIN SELECT * FROM users WHERE id = 1 END"),
3672 ("CREATE PROCEDURE TestProc AS BEGIN UPDATE users SET name = 'test' WHERE id = 1 END", "CREATE PROCEDURE TestProc AS BEGIN UPDATE users SET name = 'test' WHERE id = 1 END"),
3674 ("SELECT * FROM users WHERE id = 1 AND address = $1 and id = $2 AND deleted IS NULL AND active is TRUE", "SELECT * FROM users WHERE id = 1 AND address = $1 and id = $2 AND deleted IS NULL AND active is TRUE"),
3676 ("WITH users AS (SELECT * FROM people) SELECT * FROM users", "WITH users AS ( SELECT * FROM people ) SELECT * FROM users"),
3678 ];
3679 let mut errors = String::new();
3680 for (i, (input, expected)) in cases.iter().enumerate() {
3681 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3682 if got != *expected {
3683 errors.push_str(&format!(
3684 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3685 ));
3686 }
3687 }
3688 if !errors.is_empty() {
3689 panic!("{errors}");
3690 }
3691 }
3692
3693 #[test]
3695 fn test_suite_normalize_only_keep_sql_alias() {
3696 let config = SqlObfuscateConfig {
3697 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
3698 keep_sql_alias: true,
3699 ..Default::default()
3700 };
3701 let cases: &[(&str, &str)] = &[
3702 ("\n\t\t\t-- comment\n\t\t\t/* comment */\n\t\t\tSELECT id as id, name as n FROM users123 WHERE id in (1,2,3)", "SELECT id as id, name as n FROM users123 WHERE id in ( 1, 2, 3 )"),
3704 ];
3705 let mut errors = String::new();
3706 for (i, (input, expected)) in cases.iter().enumerate() {
3707 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3708 if got != *expected {
3709 errors.push_str(&format!(
3710 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3711 ));
3712 }
3713 }
3714 if !errors.is_empty() {
3715 panic!("{errors}");
3716 }
3717 }
3718
3719 #[test]
3721 fn test_suite_normalize_only_remove_space_between_parentheses() {
3722 let config = SqlObfuscateConfig {
3723 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
3724 remove_space_between_parentheses: true,
3725 ..Default::default()
3726 };
3727 let cases: &[(&str, &str)] = &[
3728 (
3730 "SELECT * FROM users WHERE id = 1 AND (name = 'test' OR name = 'test2')",
3731 "SELECT * FROM users WHERE id = 1 AND (name = 'test' OR name = 'test2')",
3732 ),
3733 ];
3734 let mut errors = String::new();
3735 for (i, (input, expected)) in cases.iter().enumerate() {
3736 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3737 if got != *expected {
3738 errors.push_str(&format!(
3739 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3740 ));
3741 }
3742 }
3743 if !errors.is_empty() {
3744 panic!("{errors}");
3745 }
3746 }
3747
3748 #[test]
3750 fn test_suite_normalize_only_keep_trailing_semicolon() {
3751 let config = SqlObfuscateConfig {
3752 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
3753 keep_trailing_semicolon: true,
3754 ..Default::default()
3755 };
3756 let cases: &[(&str, &str)] = &[
3757 (
3759 "SELECT * FROM users WHERE id = 1 AND name = 'test';",
3760 "SELECT * FROM users WHERE id = 1 AND name = 'test';",
3761 ),
3762 ];
3763 let mut errors = String::new();
3764 for (i, (input, expected)) in cases.iter().enumerate() {
3765 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3766 if got != *expected {
3767 errors.push_str(&format!(
3768 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3769 ));
3770 }
3771 }
3772 if !errors.is_empty() {
3773 panic!("{errors}");
3774 }
3775 }
3776
3777 #[test]
3779 fn test_suite_normalize_only_keep_identifier_quotation() {
3780 let config = SqlObfuscateConfig {
3781 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
3782 keep_identifier_quotation: true,
3783 ..Default::default()
3784 };
3785 let cases: &[(&str, &str)] = &[
3786 (
3788 "SELECT * FROM \"users\" WHERE id = 1 AND name = 'test'",
3789 "SELECT * FROM \"users\" WHERE id = 1 AND name = 'test'",
3790 ),
3791 ];
3792 let mut errors = String::new();
3793 for (i, (input, expected)) in cases.iter().enumerate() {
3794 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3795 if got != *expected {
3796 errors.push_str(&format!(
3797 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3798 ));
3799 }
3800 }
3801 if !errors.is_empty() {
3802 panic!("{errors}");
3803 }
3804 }
3805
3806 #[test]
3808 fn test_suite_obfuscate_and_normalize() {
3809 let config = SqlObfuscateConfig {
3810 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3811 ..Default::default()
3812 };
3813 let cases: &[(&str, &str)] = &[
3814 ("SELECT * FROM users WHERE id = 1", "SELECT * FROM users WHERE id = ?"),
3816 ("SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users", "SELECT ? FROM users"),
3818 ("CREATE PROCEDURE TestProc AS BEGIN SELECT * FROM users WHERE id = 1 END", "CREATE PROCEDURE TestProc AS BEGIN SELECT * FROM users WHERE id = ? END"),
3820 ("CREATE PROCEDURE TestProc AS BEGIN UPDATE users SET name = 'test' WHERE id = 1 END", "CREATE PROCEDURE TestProc AS BEGIN UPDATE users SET name = ? WHERE id = ? END"),
3822 ("SELECT * FROM users WHERE id = 1 AND address = $1 and id = $2 AND deleted IS NULL AND active is TRUE", "SELECT * FROM users WHERE id = ? AND address = ? and id = ? AND deleted IS ? AND active is ?"),
3824 ("CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(255))", "CREATE TABLE IF NOT EXISTS users ( id INT, name VARCHAR ( ? ) )"),
3826 ("SELECT * FROM users WHERE id = @P1 AND name = @P2", "SELECT * FROM users WHERE id = @P1 AND name = @P2"),
3828 ("SELECT * FROM ONLY users WHERE id = 1", "SELECT * FROM ONLY users WHERE id = ?"),
3830 ("WITH users AS (SELECT * FROM people) SELECT * FROM users where id = 1", "WITH users AS ( SELECT * FROM people ) SELECT * FROM users where id = ?"),
3832 ("SELECT * FROM users WHERE id = 1 AND name->'first' = 'test'", "SELECT * FROM users WHERE id = ? AND name -> ? = ?"),
3834 ];
3835 let mut errors = String::new();
3836 for (i, (input, expected)) in cases.iter().enumerate() {
3837 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3838 if got != *expected {
3839 errors.push_str(&format!(
3840 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3841 ));
3842 }
3843 }
3844 if !errors.is_empty() {
3845 panic!("{errors}");
3846 }
3847 }
3848
3849 #[test]
3851 fn test_suite_obfuscate_and_normalize_replace_digits() {
3852 let config = SqlObfuscateConfig {
3853 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3854 replace_digits: true,
3855 ..Default::default()
3856 };
3857 let cases: &[(&str, &str)] = &[
3858 (
3860 "SELECT * FROM users123 WHERE id = 1",
3861 "SELECT * FROM users? WHERE id = ?",
3862 ),
3863 ];
3864 let mut errors = String::new();
3865 for (i, (input, expected)) in cases.iter().enumerate() {
3866 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3867 if got != *expected {
3868 errors.push_str(&format!(
3869 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3870 ));
3871 }
3872 }
3873 if !errors.is_empty() {
3874 panic!("{errors}");
3875 }
3876 }
3877
3878 #[test]
3880 fn test_suite_obfuscate_and_normalize_keep_sql_alias() {
3881 let config = SqlObfuscateConfig {
3882 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3883 keep_sql_alias: true,
3884 ..Default::default()
3885 };
3886 let cases: &[(&str, &str)] = &[
3887 ("\n\t\t\t-- comment\n\t\t\t/* comment */\n\t\t\tSELECT id as id, name as n FROM users123 WHERE id in (1,2,3)", "SELECT id as id, name as n FROM users123 WHERE id in ( ? )"),
3889 ];
3890 let mut errors = String::new();
3891 for (i, (input, expected)) in cases.iter().enumerate() {
3892 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3893 if got != *expected {
3894 errors.push_str(&format!(
3895 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3896 ));
3897 }
3898 }
3899 if !errors.is_empty() {
3900 panic!("{errors}");
3901 }
3902 }
3903
3904 #[test]
3906 fn test_suite_obfuscate_and_normalize_dollar_quoted_func() {
3907 let config = SqlObfuscateConfig {
3908 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3909 dollar_quoted_func: true,
3910 ..Default::default()
3911 };
3912 let cases: &[(&str, &str)] = &[
3913 (
3915 "SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users",
3916 "SELECT $func$INSERT INTO table VALUES ( ? )$func$ FROM users",
3917 ),
3918 ];
3919 let mut errors = String::new();
3920 for (i, (input, expected)) in cases.iter().enumerate() {
3921 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3922 if got != *expected {
3923 errors.push_str(&format!(
3924 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3925 ));
3926 }
3927 }
3928 if !errors.is_empty() {
3929 panic!("{errors}");
3930 }
3931 }
3932
3933 #[test]
3935 fn test_suite_obfuscate_and_normalize_dollar_quoted_func_replace_digits() {
3936 let config = SqlObfuscateConfig {
3937 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3938 dollar_quoted_func: true,
3939 replace_digits: true,
3940 ..Default::default()
3941 };
3942 let cases: &[(&str, &str)] = &[
3943 (
3945 "SELECT * FROM users123 WHERE id = $tag$1$tag$",
3946 "SELECT * FROM users? WHERE id = ?",
3947 ),
3948 ];
3949 let mut errors = String::new();
3950 for (i, (input, expected)) in cases.iter().enumerate() {
3951 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3952 if got != *expected {
3953 errors.push_str(&format!(
3954 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3955 ));
3956 }
3957 }
3958 if !errors.is_empty() {
3959 panic!("{errors}");
3960 }
3961 }
3962
3963 #[test]
3965 fn test_suite_obfuscate_and_normalize_remove_space_between_parentheses() {
3966 let config = SqlObfuscateConfig {
3967 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3968 remove_space_between_parentheses: true,
3969 ..Default::default()
3970 };
3971 let cases: &[(&str, &str)] = &[
3972 (
3974 "SELECT * FROM users WHERE id = 1 AND (name = 'test' OR name = 'test2')",
3975 "SELECT * FROM users WHERE id = ? AND (name = ? OR name = ?)",
3976 ),
3977 ];
3978 let mut errors = String::new();
3979 for (i, (input, expected)) in cases.iter().enumerate() {
3980 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3981 if got != *expected {
3982 errors.push_str(&format!(
3983 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3984 ));
3985 }
3986 }
3987 if !errors.is_empty() {
3988 panic!("{errors}");
3989 }
3990 }
3991
3992 #[test]
3994 fn test_suite_obfuscate_and_normalize_keep_null() {
3995 let config = SqlObfuscateConfig {
3996 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3997 keep_null: true,
3998 ..Default::default()
3999 };
4000 let cases: &[(&str, &str)] = &[
4001 (
4003 "SELECT * FROM users WHERE id = 1 AND name IS NULL",
4004 "SELECT * FROM users WHERE id = ? AND name IS NULL",
4005 ),
4006 ];
4007 let mut errors = String::new();
4008 for (i, (input, expected)) in cases.iter().enumerate() {
4009 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4010 if got != *expected {
4011 errors.push_str(&format!(
4012 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4013 ));
4014 }
4015 }
4016 if !errors.is_empty() {
4017 panic!("{errors}");
4018 }
4019 }
4020
4021 #[test]
4023 fn test_suite_obfuscate_and_normalize_keep_boolean() {
4024 let config = SqlObfuscateConfig {
4025 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
4026 keep_boolean: true,
4027 ..Default::default()
4028 };
4029 let cases: &[(&str, &str)] = &[
4030 (
4032 "SELECT * FROM users WHERE id = 1 AND name is TRUE",
4033 "SELECT * FROM users WHERE id = ? AND name is TRUE",
4034 ),
4035 ];
4036 let mut errors = String::new();
4037 for (i, (input, expected)) in cases.iter().enumerate() {
4038 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4039 if got != *expected {
4040 errors.push_str(&format!(
4041 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4042 ));
4043 }
4044 }
4045 if !errors.is_empty() {
4046 panic!("{errors}");
4047 }
4048 }
4049
4050 #[test]
4052 fn test_suite_obfuscate_and_normalize_keep_positional_parameter() {
4053 let config = SqlObfuscateConfig {
4054 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
4055 keep_positional_parameter: true,
4056 ..Default::default()
4057 };
4058 let cases: &[(&str, &str)] = &[
4059 (
4061 "SELECT * FROM users WHERE id = 1 AND name = $1 and id = $2",
4062 "SELECT * FROM users WHERE id = ? AND name = $1 and id = $2",
4063 ),
4064 ];
4065 let mut errors = String::new();
4066 for (i, (input, expected)) in cases.iter().enumerate() {
4067 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4068 if got != *expected {
4069 errors.push_str(&format!(
4070 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4071 ));
4072 }
4073 }
4074 if !errors.is_empty() {
4075 panic!("{errors}");
4076 }
4077 }
4078
4079 #[test]
4081 fn test_suite_obfuscate_and_normalize_keep_trailing_semicolon() {
4082 let config = SqlObfuscateConfig {
4083 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
4084 keep_trailing_semicolon: true,
4085 ..Default::default()
4086 };
4087 let cases: &[(&str, &str)] = &[
4088 (
4090 "SELECT * FROM users WHERE id = 1 AND name = 'test';",
4091 "SELECT * FROM users WHERE id = ? AND name = ?;",
4092 ),
4093 ];
4094 let mut errors = String::new();
4095 for (i, (input, expected)) in cases.iter().enumerate() {
4096 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4097 if got != *expected {
4098 errors.push_str(&format!(
4099 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4100 ));
4101 }
4102 }
4103 if !errors.is_empty() {
4104 panic!("{errors}");
4105 }
4106 }
4107
4108 #[test]
4110 fn test_suite_obfuscate_and_normalize_keep_identifier_quotation() {
4111 let config = SqlObfuscateConfig {
4112 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
4113 keep_identifier_quotation: true,
4114 ..Default::default()
4115 };
4116 let cases: &[(&str, &str)] = &[
4117 (
4119 "SELECT * FROM \"users\" WHERE id = 1 AND name = 'test'",
4120 "SELECT * FROM \"users\" WHERE id = ? AND name = ?",
4121 ),
4122 ];
4123 let mut errors = String::new();
4124 for (i, (input, expected)) in cases.iter().enumerate() {
4125 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4126 if got != *expected {
4127 errors.push_str(&format!(
4128 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4129 ));
4130 }
4131 }
4132 if !errors.is_empty() {
4133 panic!("{errors}");
4134 }
4135 }
4136
4137 #[test]
4139 fn test_suite_obfuscate_and_normalize_replace_bind_parameter() {
4140 let config = SqlObfuscateConfig {
4141 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
4142 replace_bind_parameter: true,
4143 ..Default::default()
4144 };
4145 let cases: &[(&str, &str)] = &[
4146 (
4148 "SELECT * FROM users WHERE id = @P1 AND name = @P2",
4149 "SELECT * FROM users WHERE id = ? AND name = ?",
4150 ),
4151 ];
4152 let mut errors = String::new();
4153 for (i, (input, expected)) in cases.iter().enumerate() {
4154 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4155 if got != *expected {
4156 errors.push_str(&format!(
4157 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4158 ));
4159 }
4160 }
4161 if !errors.is_empty() {
4162 panic!("{errors}");
4163 }
4164 }
4165
4166 #[test]
4168 fn test_suite_obfuscate_and_normalize_keep_json_path() {
4169 let config = SqlObfuscateConfig {
4170 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
4171 keep_json_path: true,
4172 ..Default::default()
4173 };
4174 let cases: &[(&str, &str)] = &[
4175 (
4177 "SELECT * FROM users WHERE id = 1 AND name->'first' = 'test'",
4178 "SELECT * FROM users WHERE id = ? AND name -> 'first' = ?",
4179 ),
4180 (
4182 "SELECT * FROM users WHERE id = 1 AND name->>2 = 'test'",
4183 "SELECT * FROM users WHERE id = ? AND name ->> 2 = ?",
4184 ),
4185 ];
4186 let mut errors = String::new();
4187 for (i, (input, expected)) in cases.iter().enumerate() {
4188 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4189 if got != *expected {
4190 errors.push_str(&format!(
4191 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4192 ));
4193 }
4194 }
4195 if !errors.is_empty() {
4196 panic!("{errors}");
4197 }
4198 }
4199
4200 #[test]
4202 fn test_suite_obfuscate_only() {
4203 let config = SqlObfuscateConfig {
4204 obfuscation_mode: SqlObfuscationMode::ObfuscateOnly,
4205 ..Default::default()
4206 };
4207 let cases: &[(&str, &str)] = &[
4208 ("SELECT * FROM users WHERE id = 1", "SELECT * FROM users WHERE id = ?"),
4210 ("SELECT \"table\".\"field\" FROM \"table\" WHERE \"table\".\"otherfield\" = $? AND \"table\".\"thirdfield\" = $?;", "SELECT \"table\".\"field\" FROM \"table\" WHERE \"table\".\"otherfield\" = $? AND \"table\".\"thirdfield\" = $?;"),
4212 ("SELECT * FROM users123 WHERE id = 1", "SELECT * FROM users123 WHERE id = ?"),
4214 ("SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users", "SELECT ? FROM users"),
4216 ];
4217 let mut errors = String::new();
4218 for (i, (input, expected)) in cases.iter().enumerate() {
4219 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4220 if got != *expected {
4221 errors.push_str(&format!(
4222 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4223 ));
4224 }
4225 }
4226 if !errors.is_empty() {
4227 panic!("{errors}");
4228 }
4229 }
4230
4231 #[test]
4233 fn test_suite_obfuscate_only_replace_digits() {
4234 let config = SqlObfuscateConfig {
4235 obfuscation_mode: SqlObfuscationMode::ObfuscateOnly,
4236 replace_digits: true,
4237 ..Default::default()
4238 };
4239 let cases: &[(&str, &str)] = &[
4240 (
4242 "SELECT * FROM users123 WHERE id = 1",
4243 "SELECT * FROM users? WHERE id = ?",
4244 ),
4245 ];
4246 let mut errors = String::new();
4247 for (i, (input, expected)) in cases.iter().enumerate() {
4248 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4249 if got != *expected {
4250 errors.push_str(&format!(
4251 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4252 ));
4253 }
4254 }
4255 if !errors.is_empty() {
4256 panic!("{errors}");
4257 }
4258 }
4259
4260 #[test]
4262 fn test_suite_obfuscate_only_dollar_quoted_func() {
4263 let config = SqlObfuscateConfig {
4264 obfuscation_mode: SqlObfuscationMode::ObfuscateOnly,
4265 dollar_quoted_func: true,
4266 ..Default::default()
4267 };
4268 let cases: &[(&str, &str)] = &[
4269 (
4271 "SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users",
4272 "SELECT $func$INSERT INTO table VALUES (?, ?, ?)$func$ FROM users",
4273 ),
4274 ];
4275 let mut errors = String::new();
4276 for (i, (input, expected)) in cases.iter().enumerate() {
4277 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4278 if got != *expected {
4279 errors.push_str(&format!(
4280 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4281 ));
4282 }
4283 }
4284 if !errors.is_empty() {
4285 panic!("{errors}");
4286 }
4287 }
4288
4289 #[test]
4291 fn test_suite_obfuscate_only_dollar_quoted_func_replace_digits() {
4292 let config = SqlObfuscateConfig {
4293 obfuscation_mode: SqlObfuscationMode::ObfuscateOnly,
4294 dollar_quoted_func: true,
4295 replace_digits: true,
4296 ..Default::default()
4297 };
4298 let cases: &[(&str, &str)] = &[
4299 (
4301 "SELECT * FROM users123 WHERE id = $tag$1$tag$",
4302 "SELECT * FROM users? WHERE id = ?",
4303 ),
4304 ];
4305 let mut errors = String::new();
4306 for (i, (input, expected)) in cases.iter().enumerate() {
4307 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4308 if got != *expected {
4309 errors.push_str(&format!(
4310 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4311 ));
4312 }
4313 }
4314 if !errors.is_empty() {
4315 panic!("{errors}");
4316 }
4317 }
4318
4319 #[test]
4323 fn test_collapse_limit_case_insensitive() {
4324 #[allow(deprecated)]
4325 let config = SqlObfuscateConfig {
4326 obfuscation_mode: SqlObfuscationMode::Unspecified,
4327 ..Default::default()
4328 };
4329 let got_upper =
4330 super::obfuscate_sql("SELECT * FROM t LIMIT 5, 10", &config, DbmsKind::Generic);
4331 assert_eq!(
4332 got_upper, "SELECT * FROM t LIMIT ?",
4333 "uppercase LIMIT should be collapsed: {got_upper:?}"
4334 );
4335 let got_lower =
4337 super::obfuscate_sql("SELECT * FROM t limit 5, 10", &config, DbmsKind::Generic);
4338 assert_eq!(
4339 got_lower, "SELECT * FROM t limit ?",
4340 "lowercase limit should also be collapsed: {got_lower:?}"
4341 );
4342 }
4343}