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)]
53#[allow(
54 clippy::struct_excessive_bools,
55 reason = "public config schema, should not be refactored"
56)]
57pub struct SqlObfuscateConfig {
58 pub replace_digits: bool,
59 pub keep_sql_alias: bool,
60 pub dollar_quoted_func: bool,
61 pub keep_null: bool,
62 pub keep_boolean: bool,
63 pub keep_positional_parameter: bool,
64 pub keep_trailing_semicolon: bool,
65 pub keep_identifier_quotation: bool,
66 pub replace_bind_parameter: bool,
67 pub remove_space_between_parentheses: bool,
68 pub keep_json_path: bool,
69 pub obfuscation_mode: SqlObfuscationMode,
70}
71
72const fn is_whitespace(b: u8) -> bool {
73 matches!(b, b' ' | b'\t' | b'\n' | b'\r' | 0x0B | 0x0C)
74}
75
76const fn is_ident_start(b: u8) -> bool {
77 b.is_ascii_alphabetic() || b == b'_' || b > 127
78}
79
80const fn is_ident_char(b: u8) -> bool {
81 b.is_ascii_alphanumeric()
85 || b == b'_'
86 || b == b'$'
87 || b == b'#'
88 || b == b'*'
89 || b == b'@'
90 || b > 127
91}
92
93fn apply_replace_digits(ident: &str) -> String {
98 let bytes = ident.as_bytes();
99 let mut result = String::with_capacity(ident.len());
100 let mut i = 0;
101 while i < bytes.len() {
102 if bytes[i].is_ascii_digit() {
103 while i < bytes.len() && bytes[i].is_ascii_digit() {
104 i += 1;
105 }
106 result.push('?');
107 } else {
108 let c = ident[i..].chars().next().unwrap_or(' ');
110 result.push(c);
111 i += c.len_utf8();
112 }
113 }
114 result
115}
116
117fn find_quoted_string_end(bytes: &[u8], start: usize) -> Option<usize> {
119 if bytes.get(start) != Some(&b'\'') {
120 return None;
121 }
122 let short_end = {
124 let mut i = start + 1;
125 let mut result = None;
126 while i < bytes.len() {
127 if bytes[i] == b'\'' {
128 if i + 1 < bytes.len() && bytes[i + 1] == b'\'' {
129 i += 2; continue;
131 } else {
132 result = Some(i + 1);
133 break;
134 }
135 }
136 i += 1;
137 }
138 result
139 };
140
141 let short_at_boundary = short_end.is_some_and(|end| {
145 !bytes
146 .get(end)
147 .is_some_and(|&c| c.is_ascii_alphanumeric() || c == b'_')
148 });
149
150 if short_at_boundary {
151 return short_end;
152 }
153
154 let mut i = start + 1;
156 let mut escaped = false;
157 while i < bytes.len() {
158 if escaped {
159 escaped = false;
160 } else if bytes[i] == b'\\' {
161 escaped = true;
162 } else if bytes[i] == b'\'' {
163 if i + 1 < bytes.len() && bytes[i + 1] == b'\'' {
164 i += 1; } else {
166 return Some(i + 1);
167 }
168 }
169 i += 1;
170 }
171
172 short_end
174}
175
176fn find_dollar_quote_end(bytes: &[u8], start: usize) -> Option<(usize, usize, usize)> {
179 let n = bytes.len();
180 if start >= n || bytes[start] != b'$' {
181 return None;
182 }
183 let mut tag_end = start + 1;
185 while tag_end < n && bytes[tag_end] != b'$' {
186 if bytes[tag_end] == b'\n' {
187 return None; }
189 tag_end += 1;
190 }
191 if tag_end >= n {
192 return None;
193 }
194 let tag = &bytes[start..=tag_end];
196 let inner_start = tag_end + 1;
197
198 let mut i = inner_start;
200 while i + tag.len() <= n {
201 if bytes[i] == b'$' && bytes[i..].starts_with(tag) {
202 return Some((inner_start, i, i + tag.len()));
203 }
204 i += 1;
205 }
206 None
207}
208
209struct Tokenizer<'a> {
210 s: &'a str,
211 bytes: &'a [u8],
212 pos: usize,
213 result: String,
214 dbms: DbmsKind,
215 config: &'a SqlObfuscateConfig,
216 before_as_len: Option<usize>,
218 pending_savepoint: bool,
220 last_was_placeholder: bool,
223 pending_json_path: bool,
225 last_was_assign: bool,
228}
229
230impl<'a> Tokenizer<'a> {
231 fn new(s: &'a str, config: &'a SqlObfuscateConfig, dbms: DbmsKind) -> Self {
232 Self {
233 s,
234 bytes: s.as_bytes(),
235 pos: 0,
236 result: String::with_capacity(s.len()),
237 dbms,
238 config,
239 before_as_len: None,
240 pending_savepoint: false,
241 last_was_placeholder: false,
242 pending_json_path: false,
243 last_was_assign: false,
244 }
245 }
246
247 fn peek(&self, offset: usize) -> Option<u8> {
248 self.bytes.get(self.pos + offset).copied()
249 }
250
251 const fn at_end(&self) -> bool {
252 self.pos >= self.bytes.len()
253 }
254
255 const fn is_normalize_only(&self) -> bool {
256 matches!(
257 self.config.obfuscation_mode,
258 SqlObfuscationMode::NormalizeOnly
259 )
260 }
261
262 const fn is_obfuscate_only(&self) -> bool {
263 matches!(
264 self.config.obfuscation_mode,
265 SqlObfuscationMode::ObfuscateOnly
266 )
267 }
268
269 #[allow(deprecated)]
270 const fn is_unspecified_obfuscate_mode(&self) -> bool {
271 matches!(
272 self.config.obfuscation_mode,
273 SqlObfuscationMode::Unspecified
274 )
275 }
276
277 const fn last_char(&self) -> Option<u8> {
278 self.result.as_bytes().last().copied()
279 }
280
281 fn last_nonspace_char(&self) -> Option<u8> {
282 self.result
283 .as_bytes()
284 .iter()
285 .rev()
286 .find(|&&b| b != b' ')
287 .copied()
288 }
289
290 fn space(&mut self) {
295 if !self.result.is_empty()
296 && self.last_char() != Some(b' ')
297 && !(self.last_char() == Some(b'.') && {
298 let len = self.result.len();
302 if len < 2 {
303 false
304 } else {
305 let before_dot = self.result.as_bytes()[len - 2];
306 before_dot.is_ascii_alphanumeric()
307 || before_dot == b'_'
308 || before_dot == b'"'
309 || before_dot == b']'
310 || before_dot == b'#'
311 || before_dot == b'?'
312 || before_dot > 127 }
314 })
315 && !(self.last_char() == Some(b'(')
316 && (self.config.remove_space_between_parentheses || self.is_obfuscate_only()))
317 {
318 if self.last_nonspace_char() != Some(b'?') {
322 self.last_was_placeholder = false;
323 }
324 self.result.push(' ');
325 } else if self.last_char() == Some(b' ')
326 && !matches!(self.last_nonspace_char(), Some(b'?' | b'('))
327 {
328 self.last_was_placeholder = false;
332 }
333 }
334
335 fn emit(&mut self, token: &str) {
337 self.space();
338 self.result.push_str(token);
339 self.last_was_placeholder = false;
340 self.last_was_assign = false;
341 }
342
343 fn emit_char(&mut self, c: char) {
345 self.space();
346 self.result.push(c);
347 self.last_was_placeholder = c == '?';
348 self.last_was_assign = false;
349 }
350
351 fn emit_placeholder(&mut self) {
355 if self.is_unspecified_obfuscate_mode() && self.last_was_placeholder {
356 return;
358 }
359 self.emit_char('?');
360 }
361
362 fn skip_whitespace(&mut self) {
363 while !self.at_end() && is_whitespace(self.bytes[self.pos]) {
364 self.pos += 1;
365 }
366 while !self.at_end() && self.bytes[self.pos] > 127 {
368 if let Some(c) = self.s[self.pos..].chars().next() {
369 if c.is_whitespace() {
370 self.pos += c.len_utf8();
371 while !self.at_end() && is_whitespace(self.bytes[self.pos]) {
373 self.pos += 1;
374 }
375 continue;
376 }
377 }
378 break;
379 }
380 }
381
382 fn skip_line_comment(&mut self) {
383 while !self.at_end() && self.bytes[self.pos] != b'\n' {
384 self.pos += 1;
385 }
386 }
387
388 fn skip_block_comment(&mut self) {
389 while self.pos + 1 < self.bytes.len() {
391 if self.bytes[self.pos] == b'*' && self.bytes[self.pos + 1] == b'/' {
392 self.pos += 2;
393 return;
394 }
395 self.pos += 1;
396 }
397 self.pos = self.bytes.len();
399 }
400
401 fn handle_single_quote(&mut self) {
403 let str_start = self.pos;
404 if let Some(end) = find_quoted_string_end(self.bytes, self.pos) {
405 self.pos = end;
406 } else {
407 self.pos = self.bytes.len();
409 }
410 if self.pending_json_path || self.is_normalize_only() {
411 self.pending_json_path = false;
412 let raw = &self.s[str_start..self.pos].to_string();
414 if !self.maybe_consume_alias_next() {
415 self.emit(raw);
416 }
417 return;
418 }
419 if !self.maybe_consume_alias_next() {
420 self.emit_placeholder();
421 }
422 }
423
424 fn maybe_consume_alias_next(&mut self) -> bool {
428 if let Some(before_len) = self.before_as_len.take() {
429 self.result.truncate(before_len);
431 return true; }
433 false
434 }
435
436 fn emit_identifier(&mut self, ident: &str) {
438 let lower = ident.to_ascii_lowercase();
439
440 if self.pending_savepoint {
442 self.pending_savepoint = false;
443 if self.maybe_consume_alias_next() {
444 return;
445 }
446 self.emit_placeholder();
447 return;
448 }
449
450 if !self.config.keep_null && !self.is_normalize_only() && lower == "null" {
452 if self.maybe_consume_alias_next() {
453 return;
454 }
455 self.emit_placeholder();
456 return;
457 }
458
459 if !self.config.keep_boolean
461 && !self.is_normalize_only()
462 && (lower == "true" || lower == "false")
463 {
464 if self.maybe_consume_alias_next() {
465 return;
466 }
467 self.emit_placeholder();
468 return;
469 }
470
471 if !self.config.keep_sql_alias
475 && !self.is_normalize_only()
476 && !self.is_obfuscate_only()
477 && lower == "as"
478 {
479 if self.last_char() == Some(b' ') {
482 self.result.pop();
483 }
484 let before_len = self.result.len();
485 self.space();
486 self.result.push_str(ident);
487 self.before_as_len = Some(before_len);
488 self.last_was_placeholder = false;
491 return;
492 }
493
494 if self.before_as_len.is_some()
502 && matches!(
503 lower.as_str(),
504 "begin"
505 | "end"
506 | "select"
507 | "insert"
508 | "update"
509 | "delete"
510 | "from"
511 | "where"
512 | "join"
513 | "on"
514 | "set"
515 | "values"
516 | "into"
517 | "group"
518 | "order"
519 | "having"
520 | "union"
521 | "intersect"
522 | "except"
523 | "limit"
524 | "offset"
525 | "with"
526 | "create"
527 | "drop"
528 | "alter"
529 | "truncate"
530 )
531 {
532 self.before_as_len = None;
533 }
534
535 if self.maybe_consume_alias_next() {
536 return;
537 }
538
539 if lower == "savepoint" {
541 self.pending_savepoint = true;
542 }
543
544 let out = if self.config.replace_digits {
545 apply_replace_digits(ident)
546 } else {
547 ident.to_string()
548 };
549 self.emit(&out);
550 }
551
552 fn handle_dot_after_quoted_ident(&mut self) {
555 if !self.at_end() && self.bytes[self.pos] == b'.' {
556 let next = self.bytes.get(self.pos + 1).copied();
557 if self.is_obfuscate_only() {
559 self.result.push('.');
560 self.pos += 1;
561 return;
562 }
563 match next {
564 Some(b'`' | b'"' | b'[') => {
565 self.result.push_str(" . ");
566 self.pos += 1; }
568 Some(b'*') => {
569 self.result.push_str(".*");
570 self.pos += 2;
571 }
572 Some(c) if is_ident_start(c) => {
573 self.result.push_str(" . ");
574 self.pos += 1; }
576 _ => {
577 self.result.push('.');
578 self.pos += 1;
579 }
580 }
581 }
582 }
583
584 fn handle_dot_after_bracket_ident(&mut self) {
586 if !self.at_end() && self.bytes[self.pos] == b'.' {
587 let next = self.bytes.get(self.pos + 1).copied();
588 if next == Some(b'[') {
589 self.result.push_str(" . ");
590 } else {
591 self.result.push('.');
592 }
593 self.pos += 1;
594 }
595 }
596
597 fn consume_number(&mut self) {
599 self.consume_number_inner(false);
600 }
601
602 fn consume_number_inner(&mut self, seen_dot: bool) {
603 let mut saw_dot = seen_dot;
608 let mut saw_exp = false;
609 while !self.at_end() {
610 let b = self.bytes[self.pos];
611 match b {
612 b'0'..=b'9' => {
613 self.pos += 1;
614 }
615 b'.' if !saw_dot => {
616 saw_dot = true;
617 self.pos += 1;
618 }
619 b'e' | b'E' if !saw_exp => {
620 saw_exp = true;
621 self.pos += 1;
622 if !self.at_end() && matches!(self.bytes[self.pos], b'+' | b'-') {
624 self.pos += 1;
625 }
626 }
627 _ => break,
631 }
632 }
633 }
634
635 #[allow(clippy::too_many_lines, reason = "FIXME: split this function")]
636 fn process(&mut self) {
637 while !self.at_end() {
638 let b = self.bytes[self.pos];
639
640 match b {
641 b if is_whitespace(b) => {
643 self.pos += 1;
644 self.skip_whitespace();
645 if self.before_as_len.is_none() {
647 self.space();
648 }
649 }
650
651 b'-' if self.peek(1) == Some(b'-') => {
653 self.pos += 2;
654 self.skip_line_comment();
655 }
656 b'/' if self.peek(1) == Some(b'/') => {
657 self.pos += 2;
658 self.skip_line_comment();
659 }
660
661 b'#' => {
664 let next = self.peek(1);
665 let is_sqlserver = matches!(self.dbms, DbmsKind::Mssql);
666 match next {
667 Some(b)
668 if is_sqlserver
669 && (b.is_ascii_alphanumeric() || b == b'_' || b == b'#') =>
670 {
671 let start = self.pos;
673 while !self.at_end() && is_ident_char(self.bytes[self.pos]) {
674 self.pos += 1;
675 }
676 let ident = &self.s[start..self.pos];
677 if self.maybe_consume_alias_next() {
678 continue;
679 }
680 let out = if self.config.replace_digits {
681 apply_replace_digits(ident)
682 } else {
683 ident.to_string()
684 };
685 self.emit(&out);
686 }
687 Some(b'>') if matches!(self.dbms, DbmsKind::Postgresql) => {
689 if self.maybe_consume_alias_next() {
690 continue;
691 }
692 if self.peek(2) == Some(b'>') {
693 self.emit("#>>");
694 self.pos += 3;
695 } else {
696 self.emit("#>");
697 self.pos += 2;
698 }
699 self.space();
700 }
701 Some(b'-') if matches!(self.dbms, DbmsKind::Postgresql) => {
702 if self.maybe_consume_alias_next() {
703 continue;
704 }
705 self.emit("#-");
706 self.pos += 2;
707 self.space();
708 }
709 _ => {
710 self.pos += 1;
712 self.skip_line_comment();
713 }
714 }
715 }
716
717 b'/' if self.peek(1) == Some(b'*') => {
719 self.pos += 2;
720 self.skip_block_comment();
721 }
722
723 b';' => {
725 self.pos += 1;
726 if self.is_obfuscate_only()
730 || (!self.is_unspecified_obfuscate_mode()
731 && self.config.keep_trailing_semicolon)
732 {
733 if self.maybe_consume_alias_next() {
734 continue;
735 }
736 self.result.push(';');
737 } else if self.is_unspecified_obfuscate_mode() {
738 self.last_was_placeholder = true;
740 }
741 }
742
743 b'(' => {
745 if self.before_as_len.is_some() && !self.is_unspecified_obfuscate_mode() {
746 self.before_as_len = None;
748 } else if self.before_as_len.is_some() {
749 self.maybe_consume_alias_next();
752 self.pos += 1;
753 self.skip_whitespace();
754 continue; }
756 self.pending_savepoint = false;
757 self.space();
758 self.result.push('(');
759 self.pos += 1;
760 let add_space = if self.is_unspecified_obfuscate_mode() {
763 !self.is_obfuscate_only()
764 } else {
765 !self.config.remove_space_between_parentheses && !self.is_obfuscate_only()
766 };
767 if add_space {
768 self.skip_whitespace();
769 self.result.push(' ');
770 }
771 }
772
773 b')' => {
775 if self.maybe_consume_alias_next() {
776 continue;
777 }
778 let add_close_space = if self.is_unspecified_obfuscate_mode() {
780 !self.is_obfuscate_only()
781 } else {
782 !self.config.remove_space_between_parentheses && !self.is_obfuscate_only()
783 };
784 if add_close_space {
785 if !matches!(self.last_char(), Some(b'(' | b' ') | None) {
787 self.result.push(' ');
788 }
789 }
790 self.result.push(')');
791 self.pos += 1;
792 }
796
797 b',' => {
799 if self.maybe_consume_alias_next() {
800 continue;
801 }
802 self.pos += 1;
803 if self.last_was_placeholder && self.is_unspecified_obfuscate_mode() {
808 while self.last_char() == Some(b' ') {
810 self.result.pop();
811 }
812 continue;
813 }
814 if self.last_char() == Some(b' ') {
816 self.result.pop();
817 }
818 self.result.push(',');
819 }
821
822 b'\'' => {
824 self.handle_single_quote();
825 }
826
827 b'`' => {
829 self.pos += 1;
830 let mut ident_buf = String::new();
831 loop {
832 if self.at_end() {
833 break;
834 }
835 if self.bytes[self.pos] == b'`' {
836 if self.bytes.get(self.pos + 1) == Some(&b'`') {
837 ident_buf.push('`');
839 self.pos += 2;
840 } else {
841 self.pos += 1; break;
843 }
844 } else {
845 let c = self.s[self.pos..].chars().next().unwrap_or(' ');
846 ident_buf.push(c);
847 self.pos += c.len_utf8();
848 }
849 }
850 let out = if ident_buf.chars().all(char::is_whitespace) {
853 format!("`{ident_buf}`")
854 } else if self.config.replace_digits {
855 apply_replace_digits(&ident_buf)
856 } else {
857 ident_buf.clone()
858 };
859 if self.maybe_consume_alias_next() {
860 } else {
862 self.emit(&out);
863 self.handle_dot_after_quoted_ident();
864 }
865 }
866
867 b'"' => {
869 self.pos += 1;
870 let mut ident_buf = String::new();
872 while !self.at_end() {
873 if self.bytes[self.pos] == b'"' {
874 if self.bytes.get(self.pos + 1) == Some(&b'"') {
875 ident_buf.push('"'); self.pos += 2;
877 } else {
878 break;
879 }
880 } else {
881 let ch = self.s[self.pos..].chars().next().unwrap_or('\0');
882 ident_buf.push(ch);
883 self.pos += ch.len_utf8();
884 }
885 }
886 let ident_owned = ident_buf;
887 let ident = ident_owned.as_str();
888 if !self.at_end() {
889 self.pos += 1; }
891 let is_string_value = self.last_was_assign;
894 if self.pending_savepoint
896 || (!ident.is_empty() && ident.chars().all(char::is_whitespace))
897 || (!self.is_normalize_only() && is_string_value)
898 {
899 self.pending_savepoint = false;
900 if self.maybe_consume_alias_next() {
901 } else {
903 self.emit_placeholder();
904 self.handle_dot_after_quoted_ident();
905 }
906 } else if (self.config.keep_identifier_quotation
907 && !self.is_unspecified_obfuscate_mode())
908 || self.is_obfuscate_only()
909 {
910 let quoted = format!("\"{ident}\"");
914 if self.maybe_consume_alias_next() {
915 } else {
917 self.emit("ed);
918 self.handle_dot_after_quoted_ident();
919 }
920 } else {
921 let out = if ident.is_empty() {
926 format!("\"{ident}\"")
927 } else {
928 ident.to_string()
929 };
930 if self.maybe_consume_alias_next() {
931 } else {
933 self.emit(&out);
934 self.handle_dot_after_quoted_ident();
935 }
936 }
937 }
938
939 b'[' => {
941 if matches!(self.dbms, DbmsKind::Mssql) {
942 self.pos += 1;
943 let id_start = self.pos;
944 while !self.at_end() && self.bytes[self.pos] != b']' {
945 self.pos += 1;
946 }
947 let ident = &self.s[id_start..self.pos];
948 if !self.at_end() {
949 self.pos += 1; }
951 if self.maybe_consume_alias_next() {
952 } else {
954 let out = if self.config.replace_digits {
955 apply_replace_digits(ident)
956 } else {
957 ident.to_string()
958 };
959 self.emit(&out);
960 self.handle_dot_after_bracket_ident();
961 }
962 } else {
963 if self.before_as_len.is_some() {
966 self.pos += 1; while !self.at_end() && self.bytes[self.pos] != b']' {
968 self.pos += 1;
969 }
970 if !self.at_end() {
971 self.pos += 1; }
973 self.maybe_consume_alias_next();
974 } else {
975 self.space();
976 self.result.push('[');
977 self.pos += 1;
978 self.skip_whitespace();
979 self.space();
980 }
981 }
982 }
983
984 b']' => {
985 if self.maybe_consume_alias_next() {
986 continue;
987 }
988 if !matches!(self.last_char(), Some(b'[' | b' ') | None) {
989 self.space();
990 }
991 self.result.push(']');
992 self.pos += 1;
993 if !self.at_end() && self.bytes[self.pos] == b'.' {
995 self.result.push_str(" . ");
996 self.pos += 1; }
998 }
999
1000 b'$' => {
1002 let next = self.peek(1);
1003 match next {
1004 Some(b) if b.is_ascii_digit() || b == b'?' => {
1006 let token_start = self.pos;
1007 self.pos += 1; while !self.at_end()
1012 && (self.bytes[self.pos].is_ascii_digit()
1013 || self.bytes[self.pos] == b'?')
1014 {
1015 self.pos += 1;
1016 }
1017 if !self.at_end() && self.bytes[self.pos] == b'.' {
1020 self.pos += 1; while !self.at_end() && self.bytes[self.pos].is_ascii_digit() {
1022 self.pos += 1;
1023 }
1024 }
1025 if self.maybe_consume_alias_next() {
1026 continue;
1027 }
1028 let keep = (self.config.keep_positional_parameter
1033 && !self.is_unspecified_obfuscate_mode())
1034 || self.is_normalize_only()
1035 || self.is_obfuscate_only();
1036 if keep {
1037 self.emit(&self.s[token_start..self.pos]);
1038 } else {
1039 self.emit_placeholder();
1040 }
1041 }
1042 _ if next == Some(b'$')
1044 || next.is_some_and(|c| c.is_ascii_alphabetic() || c == b'_') =>
1045 {
1046 let start = self.pos;
1047 if let Some((inner_start, inner_end, outer_end)) =
1048 find_dollar_quote_end(self.bytes, start)
1049 {
1050 if self.maybe_consume_alias_next() {
1051 self.pos = outer_end;
1052 continue;
1053 }
1054 if self.is_normalize_only() {
1055 let tag_str = &self.s[start..inner_start];
1057 let inner = &self.s[inner_start..inner_end];
1058 let close_tag = &self.s[inner_end..outer_end];
1059 let normalized_inner =
1060 obfuscate_sql(inner, self.config, self.dbms);
1061 self.space();
1062 self.result.push_str(tag_str);
1063 self.result.push_str(&normalized_inner);
1064 self.result.push_str(close_tag);
1065 } else if self.config.dollar_quoted_func {
1066 let tag_str = &self.s[start..inner_start];
1068 let inner = &self.s[inner_start..inner_end];
1069 let close_tag = &self.s[inner_end..outer_end];
1070 let obfuscated_inner =
1071 obfuscate_sql(inner, self.config, self.dbms);
1072 if obfuscated_inner.trim() == "?" {
1075 self.emit_placeholder();
1076 } else {
1077 self.space();
1078 self.result.push_str(tag_str);
1079 self.result.push_str(&obfuscated_inner);
1080 self.result.push_str(close_tag);
1081 }
1082 } else {
1083 self.emit_placeholder();
1085 }
1086 self.pos = outer_end;
1087 } else {
1088 self.pos += 1; let id_start_pos = self.pos;
1092 while !self.at_end()
1093 && (is_ident_char(self.bytes[self.pos])
1094 || self.bytes[self.pos] == b'$')
1095 {
1096 self.pos += 1;
1097 }
1098 let ident = &self.s[id_start_pos - 1..self.pos]; if self.maybe_consume_alias_next() {
1100 continue;
1101 }
1102 self.emit(ident);
1103 }
1104 let _ = b; }
1106 _ => {
1107 let start = self.pos;
1109 self.pos += 1; while !self.at_end()
1111 && (is_ident_char(self.bytes[self.pos])
1112 || self.bytes[self.pos] == b'$')
1113 {
1114 self.pos += 1;
1115 }
1116 let token = &self.s[start..self.pos];
1117 if self.maybe_consume_alias_next() {
1118 continue;
1119 }
1120 self.emit(token);
1121 }
1122 }
1123 }
1124
1125 b'0' if matches!(self.peek(1), Some(b'x' | b'X')) => {
1127 self.pos += 2; while !self.at_end() && self.bytes[self.pos].is_ascii_hexdigit() {
1129 self.pos += 1;
1130 }
1131 if self.maybe_consume_alias_next() {
1132 continue;
1133 }
1134 self.emit_placeholder();
1135 }
1136
1137 b'X' | b'x' if self.peek(1) == Some(b'\'') => {
1139 self.pos += 1; if let Some(end) = find_quoted_string_end(self.bytes, self.pos) {
1141 self.pos = end;
1142 } else {
1143 self.pos += 1;
1144 }
1145 if self.maybe_consume_alias_next() {
1146 continue;
1147 }
1148 self.emit_placeholder();
1149 }
1150
1151 b'%' => {
1153 let next = self.peek(1);
1154 match next {
1155 Some(b)
1156 if b.is_ascii_alphabetic() || b == b'_' || b == b'@' || b == b'#' =>
1157 {
1158 self.pos += 2;
1161 if self.maybe_consume_alias_next() {
1162 continue;
1163 }
1164 self.emit_placeholder();
1165 }
1166 Some(b'(') => {
1167 self.pos += 2; while !self.at_end() && self.bytes[self.pos] != b')' {
1169 self.pos += 1;
1170 }
1171 if !self.at_end() {
1172 self.pos += 1;
1173 } if !self.at_end() && self.bytes[self.pos].is_ascii_alphabetic() {
1176 self.pos += 1;
1177 }
1178 if self.maybe_consume_alias_next() {
1179 continue;
1180 }
1181 self.emit_placeholder();
1182 }
1183 Some(b) if b > 127 => {
1184 let next_char = self.s[self.pos + 1..].chars().next();
1188 if let Some(nc) = next_char.filter(|c| c.is_alphabetic() || *c == '_') {
1189 let skip = 1 + nc.len_utf8();
1190 self.pos += skip;
1191 if self.maybe_consume_alias_next() {
1192 continue;
1193 }
1194 self.emit_placeholder();
1195 } else {
1196 if self.maybe_consume_alias_next() {
1197 continue;
1198 }
1199 self.space();
1200 self.result.push('%');
1201 self.pos += 1;
1202 self.space();
1203 }
1204 }
1205 _ => {
1206 if self.maybe_consume_alias_next() {
1208 continue;
1209 }
1210 self.space();
1211 self.result.push('%');
1212 self.pos += 1;
1213 self.space();
1214 }
1215 }
1216 }
1217
1218 b'.' if self.peek(1).is_some_and(|b| b.is_ascii_digit())
1223 && !self
1224 .last_char()
1225 .is_some_and(|b| is_ident_char(b) || b == b'"' || b == b'`') =>
1226 {
1227 let num_start = self.pos;
1228 self.pos += 1; self.consume_number_inner(true);
1232 if self.maybe_consume_alias_next() {
1233 continue;
1234 }
1235 if self.is_normalize_only() {
1236 let raw = self.s[num_start..self.pos].to_string();
1237 self.emit(&raw);
1238 } else {
1239 self.emit_placeholder();
1240 }
1241 }
1242
1243 b'.' => {
1248 let after_dot_is_digit = self.peek(1).is_some_and(|b| b.is_ascii_digit());
1249 let preceded_by_ident = self
1250 .last_char()
1251 .is_some_and(|b| is_ident_char(b) || b == b'"' || b == b'`');
1252 if after_dot_is_digit && preceded_by_ident {
1253 } else if !after_dot_is_digit && !preceded_by_ident {
1255 if self.maybe_consume_alias_next() {
1258 continue;
1259 }
1260 self.space();
1261 self.result.push('.');
1262 self.pos += 1;
1263 continue;
1264 }
1265 if after_dot_is_digit && preceded_by_ident {
1266 let start = self.pos; self.pos += 1; while !self.at_end()
1270 && (is_ident_char(self.bytes[self.pos]) || self.bytes[self.pos] == b'.')
1271 {
1272 if self.bytes[self.pos] == b'.' {
1273 if self
1274 .peek(1)
1275 .is_some_and(|b| b.is_ascii_digit() || is_ident_char(b))
1276 {
1277 self.pos += 1;
1278 } else {
1279 break;
1280 }
1281 } else {
1282 self.pos += 1;
1283 }
1284 }
1285 let suffix = &self.s[start..self.pos];
1286 let out = if self.config.replace_digits {
1288 apply_replace_digits(suffix)
1289 } else {
1290 suffix.to_string()
1291 };
1292 self.result.push_str(&out);
1294 } else {
1295 self.result.push('.');
1296 self.pos += 1;
1297 }
1298 }
1299
1300 b'0'..=b'9' => {
1302 let num_start = self.pos;
1303 self.consume_number();
1304 if self.maybe_consume_alias_next() {
1305 continue;
1306 }
1307 if self.pending_json_path || self.is_normalize_only() {
1308 self.pending_json_path = false;
1309 let raw = self.s[num_start..self.pos].to_string();
1310 self.emit(&raw);
1311 } else {
1312 self.emit_placeholder();
1313 }
1314 }
1315
1316 b'{' => {
1318 if self.maybe_consume_alias_next() {
1319 continue;
1320 }
1321 let mut peek_pos = self.pos + 1;
1323 while peek_pos < self.bytes.len() && is_whitespace(self.bytes[peek_pos]) {
1324 peek_pos += 1;
1325 }
1326 let is_call = peek_pos + 4 <= self.bytes.len()
1328 && self.bytes[peek_pos..peek_pos + 4].eq_ignore_ascii_case(b"call")
1329 && (peek_pos + 4 >= self.bytes.len()
1330 || !self.bytes[peek_pos + 4].is_ascii_alphanumeric());
1331 if is_call {
1332 self.space();
1333 self.result.push('{');
1334 self.pos += 1;
1335 self.skip_whitespace();
1336 self.result.push(' ');
1337 } else {
1338 let mut depth = 1usize;
1340 self.pos += 1; while !self.at_end() && depth > 0 {
1342 match self.bytes[self.pos] {
1343 b'{' => {
1344 depth += 1;
1345 self.pos += 1;
1346 }
1347 b'}' => {
1348 depth -= 1;
1349 self.pos += 1;
1350 }
1351 b'\'' => {
1352 if let Some(end) = find_quoted_string_end(self.bytes, self.pos)
1353 {
1354 self.pos = end;
1355 } else {
1356 self.pos += 1;
1357 }
1358 }
1359 _ => {
1360 self.pos += 1;
1361 }
1362 }
1363 }
1364 self.emit_placeholder();
1365 }
1366 }
1367 b'}' => {
1368 if self.maybe_consume_alias_next() {
1369 continue;
1370 }
1371 self.space();
1373 self.result.push('}');
1374 self.pos += 1;
1375 }
1376
1377 b'@' => {
1379 if self.peek(1) == Some(b'@') {
1380 let start = self.pos;
1382 self.pos += 2; while !self.at_end() && is_ident_char(self.bytes[self.pos]) {
1384 self.pos += 1;
1385 }
1386 let token = &self.s[start..self.pos];
1387 if self.maybe_consume_alias_next() {
1388 continue;
1389 }
1390 self.emit(token);
1391 } else if self.peek(1).is_some_and(|b| {
1392 b.is_ascii_alphanumeric()
1393 || b == b'_'
1394 || b == b'#'
1395 || b == b'$'
1396 || b == b'*'
1397 }) {
1398 let start = self.pos;
1400 self.pos += 1; while !self.at_end() && is_ident_char(self.bytes[self.pos]) {
1402 self.pos += 1;
1403 }
1404 let token = self.s[start..self.pos].to_string();
1405 if self.maybe_consume_alias_next() {
1406 continue;
1407 }
1408 if self.config.replace_digits && token.chars().any(|c| c.is_ascii_digit()) {
1411 let replaced = apply_replace_digits(&token);
1412 self.emit(&replaced);
1413 } else if self.config.replace_bind_parameter
1414 && !self.is_unspecified_obfuscate_mode()
1415 {
1416 self.emit_placeholder();
1417 } else {
1418 self.emit(&token);
1419 }
1420 } else if self.peek(1).is_some_and(|b| b > 127) {
1421 let next_char = self.s[self.pos + 1..].chars().next();
1423 if next_char.is_some_and(|c| c.is_alphabetic() || c == '_') {
1424 let start = self.pos;
1425 self.pos += 1; while !self.at_end() {
1427 if self.bytes[self.pos] == b'#' || self.bytes[self.pos] == b'@' {
1428 self.pos += 1;
1429 continue;
1430 }
1431 let rest = &self.s[self.pos..];
1432 match rest.chars().next() {
1433 Some(c) if c.is_alphanumeric() || c == '_' => {
1434 self.pos += c.len_utf8();
1435 }
1436 _ => break,
1437 }
1438 }
1439 let token = self.s[start..self.pos].to_string();
1440 if self.maybe_consume_alias_next() {
1441 continue;
1442 }
1443 if self.config.replace_digits {
1444 self.emit(&apply_replace_digits(&token));
1445 } else {
1446 self.emit(&token);
1447 }
1448 } else {
1449 if self.maybe_consume_alias_next() {
1450 continue;
1451 }
1452 self.space();
1453 self.result.push('@');
1454 self.pos += 1;
1455 self.last_was_placeholder = false;
1456 self.result.push(' ');
1457 }
1458 } else if self.peek(1) == Some(b'>') {
1459 if self.maybe_consume_alias_next() {
1461 continue;
1462 }
1463 self.emit("@>");
1464 self.pos += 2;
1465 self.result.push(' ');
1466 } else {
1467 if self.maybe_consume_alias_next() {
1469 continue;
1470 }
1471 self.space();
1472 self.result.push('@');
1473 self.pos += 1;
1474 self.last_was_placeholder = false;
1475 self.result.push(' ');
1476 }
1477 }
1478
1479 b':' => {
1481 match self.peek(1) {
1482 Some(b':') => {
1483 if self.maybe_consume_alias_next() {
1485 continue;
1486 }
1487 self.space();
1488 self.result.push_str("::");
1489 self.pos += 2;
1490 self.last_was_placeholder = false;
1491 self.result.push(' ');
1492 }
1493 Some(b'=') => {
1494 if self.maybe_consume_alias_next() {
1496 continue;
1497 }
1498 self.space();
1499 self.result.push_str(":=");
1500 self.pos += 2;
1501 self.last_was_placeholder = false;
1502 self.result.push(' ');
1503 }
1504 Some(b)
1505 if b.is_ascii_alphanumeric() || b == b'_' || b == b'#' || b == b'@' =>
1506 {
1507 let start = self.pos;
1510 self.pos += 1; while !self.at_end()
1513 && (is_ident_char(self.bytes[self.pos])
1514 || self.bytes[self.pos] == b'.')
1515 {
1516 self.pos += 1;
1517 }
1518 let token = &self.s[start..self.pos];
1519 if self.maybe_consume_alias_next() {
1520 continue;
1521 }
1522 self.emit(token);
1523 if token.ends_with('.') {
1527 self.result.push(' ');
1528 }
1529 }
1530 Some(b) if b > 127 => {
1531 let next_char = self.s[self.pos + 1..].chars().next();
1535 if next_char.is_some_and(|c| c.is_alphabetic() || c == '_') {
1536 let start = self.pos;
1537 self.pos += 1; while !self.at_end() {
1539 if self.bytes[self.pos] == b'#'
1543 || self.bytes[self.pos] == b'@'
1544 || self.bytes[self.pos] == b'.'
1545 {
1546 self.pos += 1;
1547 continue;
1548 }
1549 let rest = &self.s[self.pos..];
1550 match rest.chars().next() {
1551 Some(c) if c.is_alphanumeric() || c == '_' => {
1552 self.pos += c.len_utf8();
1553 }
1554 _ => break,
1555 }
1556 }
1557 let token = &self.s[start..self.pos];
1558 if self.maybe_consume_alias_next() {
1559 continue;
1560 }
1561 self.emit(token);
1562 if token.ends_with('.') {
1566 self.result.push(' ');
1567 }
1568 } else {
1569 if self.maybe_consume_alias_next() {
1570 continue;
1571 }
1572 self.space();
1573 self.result.push(':');
1574 self.pos += 1;
1575 self.last_was_placeholder = false;
1576 self.result.push(' ');
1577 }
1578 }
1579 _ => {
1580 if self.maybe_consume_alias_next() {
1582 continue;
1583 }
1584 self.space();
1585 self.result.push(':');
1586 self.pos += 1;
1587 self.last_was_placeholder = false;
1588 self.result.push(' ');
1589 }
1590 }
1591 }
1592
1593 b'-' => {
1595 match self.peek(1) {
1596 Some(b'-') => {
1597 self.pos += 2;
1599 self.skip_line_comment();
1600 }
1601 Some(b'>') if self.peek(2) == Some(b'>') => {
1602 if self.maybe_consume_alias_next() {
1604 continue;
1605 }
1606 self.emit("->>");
1607 self.pos += 3;
1608 self.result.push(' ');
1609 if self.config.keep_json_path {
1610 self.pending_json_path = true;
1611 }
1612 }
1613 Some(b'>') => {
1614 if self.maybe_consume_alias_next() {
1616 continue;
1617 }
1618 self.emit("->");
1619 self.pos += 2;
1620 self.result.push(' ');
1621 if self.config.keep_json_path {
1622 self.pending_json_path = true;
1623 }
1624 }
1625 Some(b) if b.is_ascii_digit() => {
1626 {
1630 self.pos += 1; self.consume_number();
1632 if self.maybe_consume_alias_next() {
1633 continue;
1634 }
1635 self.emit_placeholder();
1636 }
1637 }
1638 Some(b'.')
1639 if self.peek(2).is_some()
1640 && !self.peek(2).is_some_and(|d| d.is_ascii_digit()) =>
1641 {
1642 if self.maybe_consume_alias_next() {
1653 continue;
1654 }
1655 self.space();
1656 self.result.push_str("-.");
1657 self.pos += 2;
1658 self.last_was_placeholder = false;
1659 self.result.push(' ');
1660 if !self.at_end() {
1663 let c_len =
1664 self.s[self.pos..].chars().next().map_or(1, char::len_utf8);
1665 let after_c = self.pos + c_len;
1666 if after_c < self.bytes.len()
1667 && self.bytes[after_c].is_ascii_digit()
1668 {
1669 self.pos = after_c;
1671 self.consume_number();
1672 self.emit_placeholder();
1673 } else {
1674 let n = c_len.min(self.bytes.len() - self.pos);
1677 let leaked = self.s[self.pos..self.pos + n].to_owned();
1678 self.pos += n;
1679 self.result.push_str(&leaked);
1680 self.result.push(' ');
1681 }
1682 }
1683 }
1684 Some(b'.') if self.peek(2).is_some_and(|d| d.is_ascii_digit()) => {
1685 self.pos += 2; self.consume_number();
1689 if self.maybe_consume_alias_next() {
1690 continue;
1691 }
1692 self.emit_placeholder();
1693 }
1694 _ => {
1695 if self.maybe_consume_alias_next() {
1696 continue;
1697 }
1698 self.space();
1699 self.result.push('-');
1700 self.pos += 1;
1701 self.last_was_placeholder = false;
1702 self.result.push(' ');
1703 }
1704 }
1705 }
1706
1707 b'+' => {
1709 if self.maybe_consume_alias_next() {
1712 continue;
1713 }
1714 self.space();
1715 self.result.push('+');
1716 self.pos += 1;
1717 self.last_was_placeholder = false;
1718 self.result.push(' ');
1719 }
1720
1721 b'?' => {
1723 let next = self.peek(1);
1724 match next {
1725 Some(b'|') if self.peek(2) != Some(b'|') => {
1726 if self.maybe_consume_alias_next() {
1728 continue;
1729 }
1730 self.emit("?|");
1731 self.pos += 2;
1732 self.result.push(' ');
1733 }
1734 Some(b'&') => {
1735 if self.maybe_consume_alias_next() {
1737 continue;
1738 }
1739 self.emit("?&");
1740 self.pos += 2;
1741 self.result.push(' ');
1742 }
1743 _ => {
1744 if self.maybe_consume_alias_next() {
1751 continue;
1752 }
1753 if matches!(self.dbms, DbmsKind::Postgresql) {
1754 self.space();
1755 self.result.push('?');
1756 self.last_was_assign = false;
1757 } else {
1759 self.emit_placeholder();
1760 }
1761 self.pos += 1;
1762 }
1763 }
1764 }
1765
1766 b'<' => {
1768 let next = self.peek(1);
1769 if self.maybe_consume_alias_next() {
1770 continue;
1771 }
1772 match next {
1773 Some(b'@') => {
1776 let next2_is_ident = self
1777 .peek(2)
1778 .is_some_and(|c| c.is_ascii_alphanumeric() || c == b'_');
1779 if matches!(self.dbms, DbmsKind::Postgresql) || !next2_is_ident {
1780 self.emit("<@");
1781 self.pos += 2;
1782 } else {
1783 self.space();
1785 self.result.push('<');
1786 self.pos += 1;
1787 }
1788 }
1789 Some(b'>') => {
1790 self.emit("<>");
1791 self.pos += 2;
1792 }
1793 Some(b'=') => {
1794 self.emit("<=");
1795 self.pos += 2;
1796 }
1797 _ => {
1798 self.space();
1799 self.result.push('<');
1800 self.pos += 1;
1801 self.last_was_placeholder = false;
1802 }
1803 }
1804 self.result.push(' ');
1805 }
1806
1807 b'>' => {
1809 if self.maybe_consume_alias_next() {
1810 continue;
1811 }
1812 if self.peek(1) == Some(b'=') {
1813 self.emit(">=");
1814 self.pos += 2;
1815 } else {
1816 self.space();
1817 self.result.push('>');
1818 self.pos += 1;
1819 self.last_was_placeholder = false;
1820 }
1821 self.result.push(' ');
1822 }
1823
1824 b'=' => {
1826 if self.maybe_consume_alias_next() {
1827 continue;
1828 }
1829 self.space();
1830 self.result.push('=');
1831 self.pos += 1;
1832 self.last_was_placeholder = false;
1833 self.result.push(' ');
1834 self.last_was_assign = true;
1835 }
1836
1837 b'!' => {
1839 if self.maybe_consume_alias_next() {
1840 continue;
1841 }
1842 if self.peek(1) == Some(b'=') {
1843 self.emit("!=");
1844 self.pos += 2;
1845 } else if self.peek(1) == Some(b'~') {
1846 if self.peek(2) == Some(b'*') {
1847 self.emit("!~*");
1848 self.pos += 3;
1849 } else {
1850 self.emit("!~");
1851 self.pos += 2;
1852 }
1853 } else {
1854 self.space();
1855 self.result.push('!');
1856 self.pos += 1;
1857 self.last_was_placeholder = false;
1858 }
1859 self.result.push(' ');
1860 }
1861
1862 b'|' => {
1864 if self.maybe_consume_alias_next() {
1865 continue;
1866 }
1867 self.space();
1869 self.result.push('|');
1870 self.pos += 1;
1871 self.last_was_placeholder = false;
1872 self.result.push(' ');
1873 }
1874
1875 b'&' => {
1877 if self.maybe_consume_alias_next() {
1878 continue;
1879 }
1880 self.space();
1881 self.result.push('&');
1882 self.pos += 1;
1883 self.last_was_placeholder = false;
1884 self.result.push(' ');
1885 }
1886
1887 b'~' => {
1889 if self.maybe_consume_alias_next() {
1890 continue;
1891 }
1892 if self.peek(1) == Some(b'*') {
1893 self.emit("~*");
1894 self.pos += 2;
1895 } else {
1896 self.space();
1897 self.result.push('~');
1898 self.pos += 1;
1899 self.last_was_placeholder = false;
1900 }
1901 self.result.push(' ');
1902 }
1903 b'^' => {
1904 if self.maybe_consume_alias_next() {
1905 continue;
1906 }
1907 self.space();
1908 self.result.push('^');
1909 self.pos += 1;
1910 self.last_was_placeholder = false;
1911 self.result.push(' ');
1912 }
1913
1914 b'*' => {
1916 if self.maybe_consume_alias_next() {
1917 continue;
1918 }
1919 self.space();
1920 self.result.push('*');
1921 self.pos += 1;
1922 self.last_was_placeholder = false;
1923 self.result.push(' ');
1924 }
1925
1926 b'/' => {
1928 if self.maybe_consume_alias_next() {
1929 continue;
1930 }
1931 self.space();
1932 self.result.push('/');
1933 self.pos += 1;
1934 self.last_was_placeholder = false;
1935 self.result.push(' ');
1936 }
1937
1938 b if b > 127
1940 && self.s[self.pos..]
1941 .chars()
1942 .next()
1943 .is_some_and(char::is_whitespace) =>
1944 {
1945 let c = self.s[self.pos..].chars().next().unwrap_or(' ');
1946 self.pos += c.len_utf8();
1947 self.skip_whitespace();
1948 if self.before_as_len.is_none() {
1949 self.space();
1950 }
1951 }
1952
1953 _ if is_ident_start(b) || b > 127 => {
1955 let start = self.pos;
1956 while !self.at_end() {
1958 let b = self.bytes[self.pos];
1959 if b > 127 {
1960 let c = self.s[self.pos..].chars().next();
1963 if c.is_some_and(char::is_whitespace) {
1964 break;
1965 }
1966 self.pos += c.map_or(1, char::len_utf8);
1967 } else if is_ident_char(b) || b == b'.' {
1968 self.pos += 1;
1969 } else {
1970 break;
1971 }
1972 }
1973 let token = &self.s[start..self.pos];
1974 self.emit_identifier(token);
1975 if token.ends_with('.') && !self.at_end() {
1978 self.result.push(' ');
1979 }
1980 }
1981
1982 _ => {
1984 let c = self.s[self.pos..].chars().next().unwrap_or(' ');
1985 if self.maybe_consume_alias_next() {
1986 self.pos += c.len_utf8();
1987 continue;
1988 }
1989 self.result.push(c);
1990 self.pos += c.len_utf8();
1991 }
1992 }
1993 }
1994 }
1995
1996 fn finalize(mut self) -> String {
1997 while self.result.ends_with(' ') {
1999 self.result.pop();
2000 }
2001 self.result
2002 }
2003}
2004
2005const fn try_match_pure_group(bytes: &[u8], open: u8, close: u8, i: usize) -> Option<usize> {
2008 let n = bytes.len();
2009 if i >= n || bytes[i] != open {
2010 return None;
2011 }
2012 let mut k = i + 1;
2013 if k < n && bytes[k] == b' ' {
2014 k += 1;
2015 }
2016 if k >= n || bytes[k] != b'?' {
2017 return None;
2018 }
2019 k += 1;
2020 loop {
2021 if k < n && bytes[k] == b' ' {
2022 k += 1;
2023 }
2024 if k >= n {
2025 return None;
2026 }
2027 if bytes[k] == close {
2028 return Some(k + 1);
2029 }
2030 if bytes[k] == b',' {
2032 k += 1;
2033 if k < n && bytes[k] == b' ' {
2034 k += 1;
2035 }
2036 }
2037 if k < n && bytes[k] == b'?' {
2038 k += 1;
2039 } else {
2040 return None;
2041 }
2042 }
2043}
2044
2045fn collapse_grouped_values(s: &str, obfuscation_mode: SqlObfuscationMode) -> String {
2048 let bytes = s.as_bytes();
2049 let n = bytes.len();
2050 let mut result = String::with_capacity(n);
2051 let mut i = 0;
2052
2053 while i < n {
2054 if bytes[i] == b'(' {
2056 if let Some(end) = try_match_pure_group(bytes, b'(', b')', i) {
2057 result.push_str("( ? )");
2058 i = end;
2059 continue;
2060 }
2061 }
2062 if bytes[i] == b'[' {
2064 if let Some(end) = try_match_pure_group(bytes, b'[', b']', i) {
2065 result.push_str("[ ? ]");
2066 i = end;
2067 continue;
2068 }
2069 }
2070 if bytes[i] < 128 {
2072 result.push(bytes[i] as char);
2073 i += 1;
2074 } else {
2075 let c = s[i..].chars().next().unwrap_or('\u{FFFD}');
2076 result.push(c);
2077 i += c.len_utf8();
2078 }
2079 }
2080
2081 let result = collapse_multi_values(&result);
2082 #[allow(deprecated)]
2083 if matches!(obfuscation_mode, SqlObfuscationMode::Unspecified) {
2084 collapse_limit_two_args(&result)
2088 } else {
2089 result
2090 }
2091}
2092
2093fn collapse_multi_values(s: &str) -> String {
2097 let mut result = String::with_capacity(s.len());
2099 let mut remaining = s;
2100
2101 while let Some(c) = remaining.chars().next() {
2102 const VALUES_KW: &str = "VALUES";
2103 const VALUES_TAIL: &str = " ( ? )";
2104 const VALUES_FULL: &str = "VALUES ( ? )";
2105
2106 let matches_values_pattern = remaining.get(..VALUES_FULL.len()).is_some_and(|head| {
2108 head.get(..VALUES_KW.len())
2109 .is_some_and(|kw| kw.eq_ignore_ascii_case(VALUES_KW))
2110 && head
2111 .get(VALUES_KW.len()..)
2112 .is_some_and(|tail| tail == VALUES_TAIL)
2113 });
2114
2115 if matches_values_pattern {
2116 let prev_ok =
2118 result.is_empty() || matches!(result.chars().last(), Some(' ' | '(' | '\n'));
2119
2120 if prev_ok {
2121 result.push_str(&remaining[..VALUES_FULL.len()]);
2123 remaining = &remaining[VALUES_FULL.len()..];
2124
2125 loop {
2127 if let Some(rest) = remaining.strip_prefix(", ( ? )") {
2128 remaining = rest;
2129 } else if let Some(rest) = remaining.strip_prefix(" ( ? )") {
2130 remaining = rest;
2131 } else if let Some(rest) = remaining.strip_prefix(" ()") {
2132 remaining = rest;
2133 } else {
2134 break;
2135 }
2136 }
2137 continue;
2138 }
2139 }
2140
2141 result.push(c);
2142 remaining = &remaining[c.len_utf8()..];
2143 }
2144 result
2145}
2146
2147fn collapse_limit_two_args(s: &str) -> String {
2149 const PREFIX: &[u8] = b"LIMIT ?";
2150
2151 let mut result = String::with_capacity(s.len());
2153 let mut remaining = s;
2154
2155 while !remaining.is_empty() {
2156 if remaining.len() >= 9 {
2158 let rb = remaining.as_bytes();
2159 if rb[..PREFIX.len()].eq_ignore_ascii_case(PREFIX) {
2160 let skip =
2162 if remaining.len() >= 10 && rb[7] == b',' && rb[8] == b' ' && rb[9] == b'?' {
2163 Some(10) } else if rb[7] == b' ' && rb[8] == b'?' {
2165 Some(9) } else {
2167 None
2168 };
2169 if let Some(skip_len) = skip {
2170 let prev_ok = result.is_empty()
2172 || matches!(result.as_bytes().last(), Some(b' ' | b'(' | b'\n'));
2173 if prev_ok {
2174 result.push_str(&remaining[..7]); remaining = &remaining[skip_len..];
2176 continue;
2177 }
2178 }
2179 }
2180 }
2181 let c = remaining.chars().next().unwrap_or(' ');
2182 result.push(c);
2183 remaining = &remaining[c.len_utf8()..];
2184 }
2185 result
2186}
2187
2188#[must_use]
2190pub fn obfuscate_sql(s: &str, config: &SqlObfuscateConfig, dbms: DbmsKind) -> String {
2191 if s.is_empty() {
2192 return String::new();
2193 }
2194 let mut tokenizer = Tokenizer::new(s, config, dbms);
2195 tokenizer.process();
2196 let raw = tokenizer.finalize();
2197 #[allow(deprecated)]
2200 let should_collapse = matches!(
2201 config.obfuscation_mode,
2202 SqlObfuscationMode::Unspecified | SqlObfuscationMode::ObfuscateAndNormalize
2203 );
2204 if should_collapse {
2205 collapse_grouped_values(&raw, config.obfuscation_mode)
2206 } else {
2207 raw
2208 }
2209}
2210
2211#[must_use]
2213pub fn obfuscate_sql_string(s: &str) -> String {
2214 obfuscate_sql(s, &SqlObfuscateConfig::default(), DbmsKind::Generic)
2215}
2216
2217#[must_use]
2222pub fn obfuscate_sql_string_normalized(s: &str) -> String {
2223 let obfuscated = obfuscate_sql_string(s);
2224 normalize_plan_sql(&obfuscated)
2225}
2226
2227fn normalize_plan_sql(s: &str) -> String {
2228 let mut result = String::with_capacity(s.len());
2229 let mut chars = s.chars().peekable();
2230
2231 while let Some(c) = chars.next() {
2232 match c {
2233 '`' => {
2234 let identifier: String = chars.by_ref().take_while(|&c| c != '`').collect();
2236 result.push_str(&identifier);
2237
2238 if chars.peek() == Some(&'.') {
2240 chars.next(); result.push_str(if chars.peek() == Some(&'`') {
2242 " . "
2243 } else {
2244 "."
2245 });
2246 }
2247 }
2248 '(' => {
2249 result.push('(');
2250 if chars.peek().is_some_and(|&c| c != ' ') {
2251 result.push(' ');
2252 }
2253 }
2254 ')' => {
2255 if result.as_bytes().last().is_some_and(|&b| b != b' ') {
2256 result.push(' ');
2257 }
2258 result.push(')');
2259 }
2260 ':' if chars.peek() == Some(&':') => {
2261 chars.next(); if result.as_bytes().last().is_some_and(|&b| b != b' ') {
2263 result.push(' ');
2264 }
2265 result.push_str("::");
2266 if chars.peek().is_some_and(|&c| c != ' ') {
2267 result.push(' ');
2268 }
2269 }
2270 _ => result.push(c),
2271 }
2272 }
2273 result
2274}
2275
2276#[cfg(test)]
2277mod tests {
2278 use super::{DbmsKind, SqlObfuscateConfig, SqlObfuscationMode};
2279 use std::fmt::Write;
2280
2281 #[test]
2282 fn test_sql_obfuscation() {
2283 let mut panic = None;
2284 let err = CASES
2285 .iter()
2286 .enumerate()
2287 .filter_map(|(i, (input, output))| {
2288 let err =
2289 match std::panic::catch_unwind(|| test_sql_obfuscation_case(input, output)) {
2290 Ok(r) => r,
2291 Err(p) => {
2292 panic = Some(p);
2293 eprintln!("panicked case {i}\n\tinput: {input}\n\n");
2294 return None;
2295 }
2296 }
2297 .err()?;
2298 Some(format!("failed case {i}\n\terr: {err}\n"))
2299 })
2300 .collect::<String>();
2301 if !err.is_empty() {
2302 if panic.is_none() {
2303 panic!("{err}")
2304 } else {
2305 eprintln!("{err}");
2306 }
2307 }
2308 if let Some(p) = panic {
2309 std::panic::resume_unwind(p);
2310 }
2311 }
2312
2313 fn test_sql_obfuscation_case(input: &str, output: &str) -> anyhow::Result<()> {
2314 let got = super::obfuscate_sql_string(input);
2315 if output != got {
2316 anyhow::bail!("expected {output:?}\n\tgot: {got:?}")
2317 }
2318 Ok(())
2319 }
2320
2321 #[test]
2322 fn test_sql_obfuscation_normalized() {
2323 let mut panic = None;
2324 let err = NORMALIZED_CASES
2325 .iter()
2326 .enumerate()
2327 .filter_map(|(i, (input, output))| {
2328 let err = match std::panic::catch_unwind(|| {
2329 test_sql_obfuscation_normalized_case(input, output)
2330 }) {
2331 Ok(r) => r,
2332 Err(p) => {
2333 panic = Some(p);
2334 eprintln!("panicked normalized case {i}\n\tinput: {input}\n\n");
2335 return None;
2336 }
2337 }
2338 .err()?;
2339 Some(format!("failed normalized case {i}\n\terr: {err}\n"))
2340 })
2341 .collect::<String>();
2342 if !err.is_empty() {
2343 if panic.is_none() {
2344 panic!("{err}")
2345 } else {
2346 eprintln!("{err}");
2347 }
2348 }
2349 if let Some(p) = panic {
2350 std::panic::resume_unwind(p);
2351 }
2352 }
2353
2354 fn test_sql_obfuscation_normalized_case(input: &str, output: &str) -> anyhow::Result<()> {
2355 let got = super::obfuscate_sql_string_normalized(input);
2356 if output != got {
2357 anyhow::bail!("expected {output:?}\n\tgot: {got:?}")
2358 }
2359 Ok(())
2360 }
2361
2362 #[test]
2363 fn test_keep_identifier_quotation() {
2364 let config = SqlObfuscateConfig {
2365 keep_identifier_quotation: true,
2366 ..Default::default()
2367 };
2368 let got = super::obfuscate_sql(
2369 r#"SELECT * FROM "users" WHERE id = 1 AND name = 'test'"#,
2370 &config,
2371 DbmsKind::Generic,
2372 );
2373 let expected = "SELECT * FROM users WHERE id = ? AND name = ?";
2375 assert_eq!(got, expected, "keep_identifier_quotation: got {got:?}");
2376 }
2377
2378 #[test]
2379 fn test_remove_space_between_parentheses() {
2380 let config = SqlObfuscateConfig {
2381 remove_space_between_parentheses: true,
2382 ..Default::default()
2383 };
2384 let got = super::obfuscate_sql(
2385 "SELECT * FROM users WHERE id = ? AND (name = 'test' OR name = 'test2')",
2386 &config,
2387 DbmsKind::Generic,
2388 );
2389 let expected = "SELECT * FROM users WHERE id = ? AND ( name = ? OR name = ? )";
2391 assert_eq!(
2392 got, expected,
2393 "remove_space_between_parentheses: got {got:?}"
2394 );
2395 }
2396
2397 #[test]
2398 fn test_keep_positional_parameter() {
2399 let config = SqlObfuscateConfig {
2401 keep_positional_parameter: true,
2402 ..Default::default()
2403 };
2404 let got = super::obfuscate_sql(
2405 "SELECT * FROM users WHERE id = ? AND name = $1 and id = $2",
2406 &config,
2407 DbmsKind::Generic,
2408 );
2409 let expected = "SELECT * FROM users WHERE id = ? AND name = ? and id = ?";
2412 assert_eq!(
2413 got, expected,
2414 "keep_positional_parameter: got {got:?}, expected {expected:?}"
2415 );
2416 }
2417
2418 const NORMALIZED_CASES: &[(&str, &str)] = &[
2419 ("'60'::double precision", "? :: double precision"),
2421 ("'dogfood'::text", "? :: text"),
2422 ("'15531'::tid", "? :: tid"),
2423 ("(query <> 'dogfood'::text)", "( query <> ? :: text )"),
2424 ("(foo != ?)", "( foo != ? )"),
2426 ("((a >= ?) AND (b < ?))", "( ( a >= ? ) AND ( b < ? ) )"),
2427 ("?::double precision", "? :: double precision"),
2429 ("(query <> ?::text)", "( query <> ? :: text )"),
2430 ("`id`", "id"),
2432 (
2433 "(`sbtest`.`sbtest1`.`id` between ? and ?)",
2434 "( sbtest . sbtest1 . id between ? and ? )",
2435 ),
2436 (
2438 "(`sbtest`.`sbtest1`.`id` between 5016 and 5115)",
2439 "( sbtest . sbtest1 . id between ? and ? )",
2440 ),
2441 ("(query <> 'dogfood'::text)", "( query <> ? :: text )"),
2442 ("'60'::double precision", "? :: double precision"),
2443 ];
2444
2445 const CASES: &[(&str, &str)] = &[
2446 ("", ""),
2447 (" ", ""),
2448 (" ", ""),
2449 ("罿", "罿"),
2450 ("罿潯", "罿潯"),
2451 ("罿潯罿潯罿潯罿潯罿潯", "罿潯罿潯罿潯罿潯罿潯"),
2452 ("'abc1287681964'", "?"),
2453 ("-- comment", ""),
2454 ("---", ""),
2455 ("1 - 2", "? - ?"),
2456 (
2457 "SELECT * FROM TABLE WHERE userId = 'abc1287681964'",
2458 "SELECT * FROM TABLE WHERE userId = ?",
2459 ),
2460 (
2462 "SELECT * FROM TABLE WHERE userId = 'it''s a string'",
2463 "SELECT * FROM TABLE WHERE userId = ?",
2464 ),
2465 (
2466 "SELECT * FROM TABLE WHERE userId IN ('a', 'b', 'c')",
2467 "SELECT * FROM TABLE WHERE userId IN ( ? )",
2468 ),
2469 (
2470 "SELECT * FROM TABLE WHERE userId = 'abc1287681964' ORDER BY FOO DESC",
2471 "SELECT * FROM TABLE WHERE userId = ? ORDER BY FOO DESC",
2472 ),
2473 (
2476 "SELECT * FROM foo LEFT JOIN bar ON 'backslash\\' = foo.b WHERE foo.name = 'String'",
2477 "SELECT * FROM foo LEFT JOIN bar ON ? = foo.b WHERE foo.name = ?",
2478 ),
2479 (
2480 "SELECT * FROM foo LEFT JOIN bar ON 'backslash\\' = foo.b LEFT JOIN bar2 ON 'backslash2\\' = foo.b2 WHERE foo.name = 'String'",
2481 "SELECT * FROM foo LEFT JOIN bar ON ? = foo.b LEFT JOIN bar2 ON ? = foo.b2 WHERE foo.name = ?",
2482 ),
2483 (
2486 "SELECT * FROM foo LEFT JOIN bar ON 'embedded \\'quote\\' in string' = foo.b WHERE foo.name = 'String'",
2487 "SELECT * FROM foo LEFT JOIN bar ON ? = foo.b WHERE foo.name = ?",
2488 ),
2489 (
2490 "SELECT * FROM TABLE JOIN SOMETHING ON TABLE.foo = SOMETHING.bar",
2491 "SELECT * FROM TABLE JOIN SOMETHING ON TABLE.foo = SOMETHING.bar",
2492 ),
2493 (
2494 "CREATE TABLE \"VALUE\"",
2495 "CREATE TABLE VALUE",
2496 ),
2497 (
2498 "INSERT INTO \"VALUE\" (\"column\") VALUES (\'ljahklshdlKASH\')",
2499 "INSERT INTO VALUE ( column ) VALUES ( ? )",
2500 ),
2501 (
2502 "INSERT INTO \"VALUE\" (\"col1\",\"col2\",\"col3\") VALUES (\'blah\',12983,X'ff')",
2503 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2504 ),
2505 (
2506 "INSERT INTO \"VALUE\" (\"col1\", \"col2\", \"col3\") VALUES (\'blah\',12983,X'ff')",
2507 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2508 ),
2509 (
2510 "INSERT INTO VALUE (col1,col2,col3) VALUES (\'blah\',12983,X'ff')",
2511 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2512 ),
2513 (
2514 "INSERT INTO VALUE (col1,col2,col3) VALUES (12983,X'ff',\'blah\')",
2515 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2516 ),
2517 (
2518 "INSERT INTO VALUE (col1,col2,col3) VALUES (X'ff',\'blah\',12983)",
2519 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2520 ),
2521 (
2522 "INSERT INTO VALUE (col1,col2,col3) VALUES ('a',\'b\',1)",
2523 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2524 ),
2525 (
2526 "INSERT INTO VALUE (col1, col2, col3) VALUES ('a',\'b\',1)",
2527 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2528 ),
2529 (
2530 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ('a',\'b\',1)",
2531 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2532 ),
2533 (
2534 "INSERT INTO VALUE (col1,col2,col3) VALUES ('a', \'b\' ,1)",
2535 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2536 ),
2537 (
2538 "INSERT INTO VALUE (col1, col2, col3) VALUES ('a', \'b\', 1)",
2539 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2540 ),
2541 (
2542 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ('a', \'b\', 1)",
2543 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2544 ),
2545 (
2546 "INSERT INTO VALUE (col1,col2,col3) VALUES (X'ff',\'罿潯罿潯罿潯罿潯罿潯\',12983)",
2547 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2548 ),
2549 (
2550 "INSERT INTO VALUE (col1,col2,col3) VALUES (X'ff',\'罿\',12983)",
2551 "INSERT INTO VALUE ( col1, col2, col3 ) VALUES ( ? )",
2552 ),
2553 (
2555 "SELECT 3 AS NUCLEUS_TYPE,A0.ID,A0.\"NAME\" FROM \"VALUE\" A0",
2556 "SELECT ?, A0.ID, A0. NAME FROM VALUE A0",
2557 ),
2558 (
2559 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > .9999",
2560 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2561 ),
2562 (
2563 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > 0.9999",
2564 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2565 ),
2566 (
2567 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > -0.9999",
2568 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2569 ),
2570 (
2571 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > -1e6",
2572 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2573 ),
2574 (
2575 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > +1e6",
2576 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > + ?",
2577 ),
2578 (
2579 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > +255",
2580 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > + ?",
2581 ),
2582 (
2583 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > +6.34F",
2584 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > + ? F",
2585 ),
2586 (
2587 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > +6f",
2588 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > + ? f",
2589 ),
2590 (
2591 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > +0.5D",
2592 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > + ? D",
2593 ),
2594 (
2595 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > -1d",
2596 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ? d",
2597 ),
2598 (
2599 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > x'ff'",
2600 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2601 ),
2602 (
2603 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > X'ff'",
2604 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2605 ),
2606 (
2607 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > 0xff",
2608 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 > ?",
2609 ),
2610 (
2611 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> \'\'",
2612 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ?",
2613 ),
2614 (
2615 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> \' \'",
2616 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ?",
2617 ),
2618 (
2619 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> \' \'",
2620 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ?",
2621 ),
2622 (
2624 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ' x '",
2625 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ?",
2626 ),
2627 (
2628 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ' x x'",
2629 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ?",
2630 ),
2631 (
2632 "SELECT COUNT(*) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> \'5,123\'",
2633 "SELECT COUNT ( * ) FROM TABLE_1 JOIN table_2 ON TABLE_1.foo = table_2.bar where col1 <> ?",
2634 ),
2635 (
2637 "CREATE TABLE S_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))",
2638 "CREATE TABLE S_H2 ( id INTEGER not ? PRIMARY KEY ( id ) )",
2639 ),
2640 (
2641 "CREATE TABLE S_H2 ( id INTEGER not NULL, PRIMARY KEY ( id ) )",
2642 "CREATE TABLE S_H2 ( id INTEGER not ? PRIMARY KEY ( id ) )",
2643 ),
2644 (
2645 "SELECT * FROM TABLE WHERE name = 'O''Brady'",
2646 "SELECT * FROM TABLE WHERE name = ?",
2647 ),
2648 (
2649 "INSERT INTO visits VALUES (2, 8, '2013-01-02', 'rabies shot')",
2650 "INSERT INTO visits VALUES ( ? )",
2651 ),
2652 (
2653 "SELECT * FROM TABLE WHERE userId = ',' and foo=foo.bar",
2654 "SELECT * FROM TABLE WHERE userId = ? and foo = foo.bar",
2655 ),
2656 (
2657 "SELECT * FROM TABLE WHERE userId = ','||foo.bar",
2658 "SELECT * FROM TABLE WHERE userId = ? | | foo.bar",
2659 ),
2660 (
2662 "SELECT * FROM t WHERE y IN (:protocols) AND x IN (:sites)",
2663 "SELECT * FROM t WHERE y IN ( :protocols ) AND x IN ( :sites )",
2664 ),
2665 (
2667 "INSERT INTO user (id, username) VALUES ('Fred','Smith'), ('John','Smith'), ('Michael','Smith'), ('Robert','Smith');",
2668 "INSERT INTO user ( id, username ) VALUES ( ? )",
2669 ),
2670 (
2672 "INSERT INTO `qual-aa`.issues (alert0, alert1) VALUES (NULL, NULL)",
2673 "INSERT INTO qual-aa . issues ( alert0, alert1 ) VALUES ( ? )",
2674 ),
2675 (
2677 "select !+2",
2678 "select ! + ?",
2679 ),
2680 (
2684 "SELECT 5*s1 FROM t4",
2685 "SELECT ? * s1 FROM t4",
2686 ),
2687 (
2688 "(SELECT 5*s1 FROM t4 UNION SELECT 77 FROM t5)",
2689 "( SELECT ? * s1 FROM t4 UNION SELECT ? FROM t5 )",
2690 ),
2691 (
2693 "WHERE ROW(5*t2.s1,77)=(SELECT 5*s1 FROM t4 UNION SELECT 77 FROM (SELECT * FROM t5))",
2694 "WHERE ROW ( ? * t2.s1, ? ) = ( SELECT ? * s1 FROM t4 UNION SELECT ? FROM ( SELECT * FROM t5 ) )",
2695 ),
2696 (
2698 "UPDATE user_dash_pref SET json_prefs = %(json_prefs)s, modified = '2015-08-27' WHERE user_id = %(user_id)s AND url = %(url)s",
2699 "UPDATE user_dash_pref SET json_prefs = ? modified = ? WHERE user_id = ? AND url = ?",
2700 ),
2701 (
2703 "UPDATE t SET a = 1, b = 2, c = 3",
2704 "UPDATE t SET a = ? b = ? c = ?",
2705 ),
2706 (
2708 "SELECT set_config('foo', bar, FALSE)",
2709 "SELECT set_config ( ? bar, ? )",
2710 ),
2711 (":ჸ", ":ჸ"),
2713 ("%ჸ", "?"),
2715 ("%C", "?"),
2717 (".ჸ", ". ჸ"),
2719 ("0!(2", "? ! ( ?"),
2721 ("0(($0", "? ( ("),
2723 ("0D", "? D"),
2725 ("@ᏤᏤ", "@ᏤᏤ"),
2727 ("ჸ*", "ჸ*"),
2729 (".*", ". *"),
2731 ("table.*", "table.*"),
2732 ("(ჷ", "( ჷ"),
2734 ("2%$2", "? % ?"),
2735 ];
2736
2737 #[test]
2738 fn test_normalize_only() {
2739 let config = SqlObfuscateConfig {
2740 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
2741 ..Default::default()
2742 };
2743 let cases = &[
2744 (
2746 "SELECT * FROM users WHERE id = 1",
2747 "SELECT * FROM users WHERE id = 1",
2748 ),
2749 (
2751 "SELECT * FROM users WHERE id = 1 AND name = 'test'",
2752 "SELECT * FROM users WHERE id = 1 AND name = 'test'",
2753 ),
2754 (
2756 "-- comment\n/* comment */\nSELECT id as id, name as n FROM users123 WHERE id in (1,2,3)",
2757 "SELECT id as id, name as n FROM users123 WHERE id in ( 1, 2, 3 )",
2758 ),
2759 (
2761 "WITH users AS (SELECT * FROM people) SELECT * FROM users",
2762 "WITH users AS ( SELECT * FROM people ) SELECT * FROM users",
2763 ),
2764 (
2766 "SELECT * FROM users WHERE id = 1 AND address = $1 and id = $2 AND deleted IS NULL AND active is TRUE",
2767 "SELECT * FROM users WHERE id = 1 AND address = $1 and id = $2 AND deleted IS NULL AND active is TRUE",
2768 ),
2769 (
2773 "SELECT * FROM users WHERE id = 1;",
2774 "SELECT * FROM users WHERE id = 1",
2775 ),
2776 ];
2777 for (input, expected) in cases {
2778 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
2779 assert_eq!(got, *expected, "normalize_only input={input:?}");
2780 }
2781 }
2782
2783 #[test]
2784 fn test_normalize_only_keep_trailing_semi() {
2785 let config = SqlObfuscateConfig {
2786 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
2787 keep_trailing_semicolon: true,
2788 ..Default::default()
2789 };
2790 let got = super::obfuscate_sql(
2791 "SELECT * FROM users WHERE id = 1 AND name = 'test';",
2792 &config,
2793 DbmsKind::Generic,
2794 );
2795 let expected = "SELECT * FROM users WHERE id = 1 AND name = 'test';";
2796 assert_eq!(
2797 got, expected,
2798 "normalize_only+keep_trailing_semicolon: {got:?}"
2799 );
2800 }
2801
2802 #[test]
2803 fn test_normalize_only_keep_identifier_quotation() {
2804 let config = SqlObfuscateConfig {
2805 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
2806 keep_identifier_quotation: true,
2807 ..Default::default()
2808 };
2809 let got = super::obfuscate_sql(
2810 r#"SELECT * FROM "users" WHERE id = 1 AND name = 'test'"#,
2811 &config,
2812 DbmsKind::Generic,
2813 );
2814 let expected = r#"SELECT * FROM "users" WHERE id = 1 AND name = 'test'"#;
2815 assert_eq!(
2816 got, expected,
2817 "normalize_only+keep_identifier_quotation: {got:?}"
2818 );
2819 }
2820
2821 #[test]
2822 fn test_with_cte_stripping() {
2823 let config = SqlObfuscateConfig::default();
2825 let cases = &[
2826 (
2828 "WITH sales AS (SELECT x FROM t WHERE id = 1) SELECT * FROM sales",
2829 "WITH sales SELECT x FROM t WHERE id = ? ) SELECT * FROM sales",
2830 ),
2831 (
2833 "WITH T1 AS (SELECT a FROM t1 WHERE id = 1), T2 AS (SELECT b FROM t2) SELECT * FROM T1",
2834 "WITH T1 SELECT a FROM t1 WHERE id = ? ) T2 SELECT b FROM t2 ) SELECT * FROM T1",
2835 ),
2836 ];
2837 for (input, expected) in cases {
2838 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
2839 assert_eq!(got, *expected, "with_cte_stripping input={input:?}");
2840 }
2841 }
2842
2843 #[test]
2844 fn test_double_quoted_string_value_quantize() {
2845 let config = SqlObfuscateConfig::default();
2847 let cases = &[
2848 (
2850 r#"update Orders set created = "2019-05-24 00:26:17", gross = 30.28"#,
2851 "update Orders set created = ? gross = ?",
2852 ),
2853 (
2855 r#"update Orders set payment_type = "eventbrite""#,
2856 "update Orders set payment_type = ?",
2857 ),
2858 (
2860 r#"SELECT * FROM "users" WHERE id = 1"#,
2861 r"SELECT * FROM users WHERE id = ?",
2862 ),
2863 ];
2864 for (input, expected) in cases {
2865 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
2866 assert_eq!(got, *expected, "double_quoted_value input={input:?}");
2867 }
2868 }
2869
2870 #[test]
2871 fn test_normalize_only_dollar_func() {
2872 let config = SqlObfuscateConfig {
2874 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
2875 ..Default::default()
2876 };
2877 let got = super::obfuscate_sql(
2878 "SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users",
2879 &config,
2880 DbmsKind::Generic,
2881 );
2882 let expected = "SELECT $func$INSERT INTO table VALUES ( 'a', 1, 2 )$func$ FROM users";
2883 assert_eq!(got, expected, "normalize_only dollar_func: {got:?}");
2884 }
2885
2886 #[test]
2887 fn test_dollar_quoted_func_trivial_collapse() {
2888 let config = SqlObfuscateConfig {
2890 dollar_quoted_func: true,
2891 replace_digits: true,
2892 ..Default::default()
2893 };
2894 let got = super::obfuscate_sql(
2895 "SELECT * FROM users123 WHERE id = $tag$1$tag$",
2896 &config,
2897 DbmsKind::Generic,
2898 );
2899 let expected = "SELECT * FROM users? WHERE id = ?";
2900 assert_eq!(
2901 got, expected,
2902 "dollar_quoted_func trivial collapse: {got:?}"
2903 );
2904 }
2905
2906 #[test]
2907 fn test_obfuscate_only_keeps_quotes_and_semi() {
2908 let config = SqlObfuscateConfig {
2910 obfuscation_mode: SqlObfuscationMode::ObfuscateOnly,
2911 ..Default::default()
2912 };
2913 let got = super::obfuscate_sql(
2914 r#"SELECT "table"."field" FROM "table" WHERE "table"."otherfield" = $? AND "table"."thirdfield" = $?;"#,
2915 &config,
2916 DbmsKind::Generic,
2917 );
2918 let expected = r#"SELECT "table"."field" FROM "table" WHERE "table"."otherfield" = $? AND "table"."thirdfield" = $?;"#;
2919 assert_eq!(got, expected, "obfuscate_only keeps quotes/$/semi: {got:?}");
2920 }
2921
2922 #[test]
2923 fn test_obfuscate_only_dollar_quoted_func_no_collapse() {
2924 let config = SqlObfuscateConfig {
2926 obfuscation_mode: SqlObfuscationMode::ObfuscateOnly,
2927 dollar_quoted_func: true,
2928 ..Default::default()
2929 };
2930 let got = super::obfuscate_sql(
2931 "SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users",
2932 &config,
2933 DbmsKind::Generic,
2934 );
2935 let expected = "SELECT $func$INSERT INTO table VALUES (?, ?, ?)$func$ FROM users";
2936 assert_eq!(
2937 got, expected,
2938 "obfuscate_only dollar_quoted_func no collapse: {got:?}"
2939 );
2940 }
2941
2942 #[test]
2943 fn test_normalize_only_procedure() {
2944 let config = SqlObfuscateConfig {
2945 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
2946 ..Default::default()
2947 };
2948 let got = super::obfuscate_sql(
2949 "CREATE PROCEDURE TestProc AS BEGIN UPDATE users SET name = 'test' WHERE id = 1 END",
2950 &config,
2951 DbmsKind::Generic,
2952 );
2953 let expected =
2954 "CREATE PROCEDURE TestProc AS BEGIN UPDATE users SET name = 'test' WHERE id = 1 END";
2955 assert_eq!(got, expected, "normalize_only+procedure: {got:?}");
2956 }
2957
2958 #[test]
2959 fn test_q41() {
2960 let config = SqlObfuscateConfig::default();
2961 let input = "SELECT * FROM public.table ( array [ ROW ( array [ 'magic', 'foo',";
2962 let mut tok = super::Tokenizer::new(input, &config, DbmsKind::Generic);
2964 tok.process();
2965 let raw = tok.finalize();
2966 eprintln!("RAW: {raw:?}");
2967 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
2968 let expected = "SELECT * FROM public.table ( array [ ROW ( array [ ?";
2969 assert_eq!(got, expected, "q41: {got:?}");
2970 }
2971
2972 #[test]
2975 fn test_pg_json_operators_7() {
2976 let got = super::obfuscate_sql(
2978 "select * from users where user.custom ? 'foo'",
2979 &SqlObfuscateConfig::default(),
2980 DbmsKind::Postgresql,
2981 );
2982 let expected = "select * from users where user.custom ? ?";
2983 assert_eq!(got, expected, "pg_json_7: {got:?}");
2984 }
2985
2986 #[test]
2987 fn test_quantizer_90() {
2988 let config = SqlObfuscateConfig::default();
2990 let got = super::obfuscate_sql(
2991 "SELECT * FROM dbo.Items WHERE id = 1 or /*!obfuscation*/ 1 = 1",
2992 &config,
2993 DbmsKind::Generic,
2994 );
2995 let expected = "SELECT * FROM dbo.Items WHERE id = ? or ? = ?";
2996 assert_eq!(got, expected, "q90: {got:?}");
2997 }
2998
2999 #[test]
3000 fn test_cassandra_nested_dates() {
3001 let config = SqlObfuscateConfig::default();
3003 let got = super::obfuscate_sql(
3004 "SELECT TO_DATE(TO_CHAR(TO_DATE(bar.h,?),?),?) FROM t",
3005 &config,
3006 DbmsKind::Generic,
3007 );
3008 let expected = "SELECT TO_DATE ( TO_CHAR ( TO_DATE ( bar.h, ? ) ) ) FROM t";
3009 assert_eq!(got, expected, "cassandra_nested_dates: {got:?}");
3010 }
3011
3012 #[test]
3013 fn test_cassandra_pipe_concat() {
3014 let config = SqlObfuscateConfig::default();
3016 let got = super::obfuscate_sql("SELECT a ||?|| b FROM t", &config, DbmsKind::Generic);
3017 let expected = "SELECT a | | ? | | b FROM t";
3018 assert_eq!(got, expected, "cassandra_pipe: {got:?}");
3019 }
3020
3021 const SUITE_CASES: &[(&str, &str)] = &[
3023 ("SELECT username AS person FROM users WHERE id=4", "SELECT username FROM users WHERE id = ?"),
3025 ("autovacuum: VACUUM ANALYZE fake.table", "autovacuum : VACUUM ANALYZE fake.table"),
3027 ("autovacuum: VACUUM ANALYZE fake.table_downtime", "autovacuum : VACUUM ANALYZE fake.table_downtime"),
3029 ("autovacuum: VACUUM fake.big_table (to prevent wraparound)", "autovacuum : VACUUM fake.big_table ( to prevent wraparound )"),
3031 ("SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users", "SELECT ? FROM users"),
3033 ("\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"),
3035 ("\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"),
3037 ("\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"),
3039 ("\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 = ?"),
3041 ("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 + ? ) )"),
3043 ("\n-- Testing table value constructor SQL expression\nSELECT * FROM (VALUES (1, 'dog')) AS d (id, animal)", "SELECT * FROM ( VALUES ( ? ) ) ( id, animal )"),
3045 ("ALTER TABLE table DROP COLUMN column", "ALTER TABLE table DROP COLUMN column"),
3047 ("REVOKE ALL ON SCHEMA datadog FROM datadog", "REVOKE ALL ON SCHEMA datadog FROM datadog"),
3049 ("TRUNCATE TABLE datadog", "TRUNCATE TABLE datadog"),
3051 ("\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"),
3053 ("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"),
3055 (" 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"),
3057 ("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 ? = ?"),
3059 ("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 = ? )"),
3061 ("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"),
3063 ("select * from dollarField$ as df from some$dollar$filled_thing$$;", "select * from dollarField$ from some$dollar$filled_thing$$"),
3065 ("select * from `構わない`;", "select * from 構わない"),
3067 ("select * from names where name like '�����';", "select * from names where name like ?"),
3069 ("select replacement from table where replacement = 'i�n�t�e��rspersed';", "select replacement from table where replacement = ?"),
3071 ("SELECT ('�');", "SELECT ( ? )"),
3073 ("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 ], ? )"),
3075 ("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 = ?"),
3077 ("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"),
3079 ("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"),
3081 ("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"),
3083 ("SELECT \"table\".\"field\" FROM \"table\" WHERE \"table\".\"otherfield\" = $? AND \"table\".\"thirdfield\" = $?;", "SELECT table . field FROM table WHERE table . otherfield = ? AND table . thirdfield = ?"),
3085 ("select * from users where id = 42", "select * from users where id = ?"),
3087 ("select * from users where float = .43422", "select * from users where float = ?"),
3089 ("SELECT host, status FROM ec2_status WHERE org_id = 42", "SELECT host, status FROM ec2_status WHERE org_id = ?"),
3091 ("SELECT host, status FROM ec2_status WHERE org_id=42", "SELECT host, status FROM ec2_status WHERE org_id = ?"),
3093 ("-- get user \n--\n select * \n from users \n where\n id = 214325346", "select * from users where id = ?"),
3095 ("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 ( ? )"),
3097 ("SELECT `host`.`address` FROM `host` WHERE org_id=42", "SELECT host . address FROM host WHERE org_id = ?"),
3099 ("SELECT \"host\".\"address\" FROM \"host\" WHERE org_id=42", "SELECT host . address FROM host WHERE org_id = ?"),
3101 ("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 ( ? )"),
3103 ("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 = ?"),
3105 ("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 ( ? )"),
3107 ("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 [ ? ] )"),
3109 ("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 [ ? ] )"),
3111 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 1", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3113 ("SELECT articles.* FROM articles WHERE articles.id = 1 limit 1", "SELECT articles.* FROM articles WHERE articles.id = ? limit ?"),
3115 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 1, 20", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3117 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 1, 20;", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3119 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 1, 20;", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3121 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 15,20;", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3123 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 1;", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3125 ("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 ? )"),
3127 ("SELECT articles.* FROM articles WHERE (articles.created_at BETWEEN $1 AND $2)", "SELECT articles.* FROM articles WHERE ( articles.created_at BETWEEN ? AND ? )"),
3129 ("SELECT articles.* FROM articles WHERE (articles.published != true)", "SELECT articles.* FROM articles WHERE ( articles.published != ? )"),
3131 ("SELECT articles.* FROM articles WHERE (title = 'guides.rubyonrails.org')", "SELECT articles.* FROM articles WHERE ( title = ? )"),
3133 ("SELECT articles.* FROM articles WHERE ( title = ? ) AND ( author = ? )", "SELECT articles.* FROM articles WHERE ( title = ? ) AND ( author = ? )"),
3135 ("SELECT articles.* FROM articles WHERE ( title = :title )", "SELECT articles.* FROM articles WHERE ( title = :title )"),
3137 ("SELECT articles.* FROM articles WHERE ( title = @title )", "SELECT articles.* FROM articles WHERE ( title = @title )"),
3139 ("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 ) > ?"),
3141 ("SELECT * FROM articles WHERE id > 10 ORDER BY id asc LIMIT 20", "SELECT * FROM articles WHERE id > ? ORDER BY id asc LIMIT ?"),
3143 ("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 = ?"),
3145 ("SELECT articles.* FROM articles WHERE articles.id IN (1, 3, 5)", "SELECT articles.* FROM articles WHERE articles.id IN ( ? )"),
3147 ("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"),
3149 ("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"),
3151 ("SAVEPOINT \"s139956586256192_x1\"", "SAVEPOINT ?"),
3153 ("INSERT INTO user (id, username) VALUES ('Fred','Smith'), ('John','Smith'), ('Michael','Smith'), ('Robert','Smith');", "INSERT INTO user ( id, username ) VALUES ( ? )"),
3155 ("CREATE KEYSPACE Excelsior WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 3};", "CREATE KEYSPACE Excelsior WITH replication = ?"),
3157 ("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 ?"),
3159 ("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 = ?"),
3161 ("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 ( ? )"),
3163 ("SELECT name, pretty_print(address) FROM people;", "SELECT name, pretty_print ( address ) FROM people"),
3165 ("* SELECT * FROM fake_data(1, 2, 3);", "* SELECT * FROM fake_data ( ? )"),
3167 ("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"),
3169 ("SELECT * FROM public.table ( array [ ROW ( array [ 'magic', 'foo',", "SELECT * FROM public.table ( array [ ROW ( array [ ?"),
3171 ("SELECT pg_try_advisory_lock (123) AS t46eef3f025cc27feb31ca5a2d668a09a", "SELECT pg_try_advisory_lock ( ? )"),
3173 ("INSERT INTO `qual-aa`.issues (alert0 , alert1) VALUES (NULL, NULL)", "INSERT INTO qual-aa . issues ( alert0, alert1 ) VALUES ( ? )"),
3175 ("INSERT INTO user (id, email, name) VALUES (null, ?, ?)", "INSERT INTO user ( id, email, name ) VALUES ( ? )"),
3177 ("select * from users where id = 214325346 # This comment continues to the end of line", "select * from users where id = ?"),
3179 ("select * from users where id = 214325346 -- This comment continues to the end of line", "select * from users where id = ?"),
3181 ("SELECT * FROM /* this is an in-line comment */ users;", "SELECT * FROM users"),
3183 ("SELECT /*! STRAIGHT_JOIN */ col1 FROM table1", "SELECT col1 FROM table1"),
3185 ("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 ) ) ) )"),
3187 ("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 = ?"),
3189 ("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"),
3191 (" 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 = ?"),
3193 ("SELECT [b].[BlogId], [b].[Name]\nFROM [Blogs] AS [b]\nORDER BY [b].[Name]", "SELECT [ b ] . [ BlogId ], [ b ] . [ Name ] FROM [ Blogs ] ORDER BY [ b ] . [ Name ]"),
3195 ("SELECT * FROM users WHERE firstname=''", "SELECT * FROM users WHERE firstname = ?"),
3197 ("SELECT * FROM users WHERE firstname=' '", "SELECT * FROM users WHERE firstname = ?"),
3199 ("SELECT * FROM users WHERE firstname=\"\"", "SELECT * FROM users WHERE firstname = ?"),
3201 ("SELECT * FROM users WHERE lastname=\" \"", "SELECT * FROM users WHERE lastname = ?"),
3203 ("SELECT * FROM users WHERE lastname=\"\t \"", "SELECT * FROM users WHERE lastname = ?"),
3205 ("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"),
3207 ("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 = ?"),
3209 ("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 = ?"),
3211 ("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 = ?"),
3213 ("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 = ?"),
3215 ("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 = ?"),
3217 ("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 = ?"),
3219 ("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 = ?"),
3221 ("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 [ ? ] )"),
3223 ("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 = ?"),
3225 ("SELECT a :: VARCHAR(255) FROM foo WHERE foo.name = 'String'", "SELECT a :: VARCHAR ( ? ) FROM foo WHERE foo.name = ?"),
3227 ("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 ) = ( ? ) ) )"),
3229 ("{call px_cu_se_security_pg.sps_get_my_accounts_count(?, ?, ?, ?)}", "{ call px_cu_se_security_pg.sps_get_my_accounts_count ( ? ) }"),
3231 ("{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 ( ? ) }"),
3233 ("{call curly_fun('{{', '}}', '}', '}')};", "{ call curly_fun ( ? ) }"),
3235 ("SELECT id, name FROM emp WHERE name LIKE {fn UCASE('Smith')}", "SELECT id, name FROM emp WHERE name LIKE ?"),
3237 ("select users.custom #- '{a,b}' from users", "select users.custom"),
3239 ("select users.custom #> '{a,b}' from users", "select users.custom"),
3241 ("select users.custom #>> '{a,b}' from users", "select users.custom"),
3243 ("SELECT a FROM foo WHERE value<@name", "SELECT a FROM foo WHERE value < @name"),
3245 ("SELECT @@foo", "SELECT @@foo"),
3247 ("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 ( ? )"),
3249 ("\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 ?"),
3251 ("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 ( )"),
3253 ("INSERT INTO table (field1, field2) VALUES (1, $$someone's string123$with other things$$)", "INSERT INTO table ( field1, field2 ) VALUES ( ? )"),
3255 ("INSERT INTO table (field1) VALUES ($some tag$this text confuses$some other text$some ta not quite$some tag$)", "INSERT INTO table ( field1 ) VALUES ( ? )"),
3257 ("INSERT INTO table (field1) VALUES ($tag$random \\wqejks \"sadads' text$tag$)", "INSERT INTO table ( field1 ) VALUES ( ? )"),
3259 ("SELECT nspname FROM pg_class where nspname !~ '.*toIgnore.*'", "SELECT nspname FROM pg_class where nspname !~ ?"),
3261 ("SELECT nspname FROM pg_class where nspname !~* '.*toIgnoreInsensitive.*'", "SELECT nspname FROM pg_class where nspname !~* ?"),
3263 ("SELECT nspname FROM pg_class where nspname ~ '.*matching.*'", "SELECT nspname FROM pg_class where nspname ~ ?"),
3265 ("SELECT nspname FROM pg_class where nspname ~* '.*matchingInsensitive.*'", "SELECT nspname FROM pg_class where nspname ~* ?"),
3267 ("SELECT * FROM dbo.Items WHERE id = 1 or /*!obfuscation*/ 1 = 1", "SELECT * FROM dbo.Items WHERE id = ? or ? = ?"),
3269 ("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 = ?"),
3271 ("USING $09 SELECT", "USING ? SELECT"),
3273 ("USING - SELECT", "USING - SELECT"),
3275 ("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 ( ? )"),
3277 ("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 ( ? )"),
3279 ("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 ( ? )"),
3281 ("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 = ?"),
3283 ("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 <= ?"),
3285 ("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 ) ) )"),
3287 ("1234", "?"),
3289 ("-1234", "?"),
3291 ("1234e12", "?"),
3293 ("0xfa", "?"),
3295 ("01234567", "?"),
3297 ("09", "?"),
3299 ("-01234567", "?"),
3301 ("-012345678", "?"),
3303 ];
3304
3305 #[test]
3306 fn test_sql_obfuscation_suite() {
3307 let mut errors = String::new();
3308 for (i, (input, expected)) in SUITE_CASES.iter().enumerate() {
3309 let got = super::obfuscate_sql_string(input);
3310 if got != *expected {
3311 let _ = write!(
3312 errors,
3313 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3314 );
3315 }
3316 }
3317 assert!(errors.is_empty(), "{errors}");
3318 }
3319
3320 #[test]
3322 #[allow(deprecated)]
3323 fn test_suite_keep_sql_alias() {
3324 let config = SqlObfuscateConfig {
3325 keep_sql_alias: true,
3326 ..Default::default()
3327 };
3328 let cases: &[(&str, &str)] = &[
3329 (
3331 "SELECT username AS person FROM users WHERE id=4",
3332 "SELECT username AS person FROM users WHERE id = ?",
3333 ),
3334 ];
3335 let mut errors = String::new();
3336 for (i, (input, expected)) in cases.iter().enumerate() {
3337 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3338 if got != *expected {
3339 let _ = write!(
3340 errors,
3341 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3342 );
3343 }
3344 }
3345 assert!(errors.is_empty(), "{errors}");
3346 }
3347
3348 #[test]
3350 #[allow(deprecated)]
3351 fn test_suite_dollar_quoted_func() {
3352 let config = SqlObfuscateConfig {
3353 dollar_quoted_func: true,
3354 ..Default::default()
3355 };
3356 let cases: &[(&str, &str)] = &[
3357 (
3359 "SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users",
3360 "SELECT $func$INSERT INTO table VALUES ( ? )$func$ FROM users",
3361 ),
3362 ];
3363 let mut errors = String::new();
3364 for (i, (input, expected)) in cases.iter().enumerate() {
3365 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3366 if got != *expected {
3367 let _ = write!(
3368 errors,
3369 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3370 );
3371 }
3372 }
3373 assert!(errors.is_empty(), "{errors}");
3374 }
3375
3376 #[test]
3378 #[allow(deprecated)]
3379 fn test_suite_keep_sql_alias_dollar_quoted_func() {
3380 let config = SqlObfuscateConfig {
3381 keep_sql_alias: true,
3382 dollar_quoted_func: true,
3383 ..Default::default()
3384 };
3385 let cases: &[(&str, &str)] = &[
3386 ("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 ( )"),
3388 ];
3389 let mut errors = String::new();
3390 for (i, (input, expected)) in cases.iter().enumerate() {
3391 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3392 if got != *expected {
3393 let _ = write!(
3394 errors,
3395 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3396 );
3397 }
3398 }
3399 assert!(errors.is_empty(), "{errors}");
3400 }
3401
3402 #[test]
3404 #[allow(deprecated)]
3405 fn test_suite_replace_digits() {
3406 let config = SqlObfuscateConfig {
3407 replace_digits: true,
3408 ..Default::default()
3409 };
3410 let cases: &[(&str, &str)] = &[
3411 ("\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"),
3413 ("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 ], ? )"),
3415 ("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 = ?"),
3417 ("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"),
3419 ("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"),
3421 ("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"),
3423 ("select * from users where id = 42", "select * from users where id = ?"),
3425 ("select * from `backslashes` where id = 42", "select * from backslashes where id = ?"),
3427 ("select * from \"double-quotes\" where id = 42", "select * from double-quotes where id = ?"),
3429 ("SELECT host, status FROM ec2_status WHERE org_id = 42", "SELECT host, status FROM ec?_status WHERE org_id = ?"),
3431 ("SELECT * FROM (SELECT * FROM nested_table)", "SELECT * FROM ( SELECT * FROM nested_table )"),
3433 (" -- get user \n--\n select * \n from users \n where\n id = 214325346 ", "select * from users where id = ?"),
3435 ("SELECT articles.* FROM articles WHERE articles.id = 1 LIMIT 1, 20", "SELECT articles.* FROM articles WHERE articles.id = ? LIMIT ?"),
3437 ("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 = ?"),
3439 ("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 ( ? )"),
3441 ("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 = ?"),
3443 ("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"),
3445 ("DELETE FROM table WHERE table.a=1", "DELETE FROM table WHERE table.a = ?"),
3447 ("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 = ?"),
3449 ("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 ], ? )"),
3451 ("SELECT name FROM people WHERE person_id = -1", "SELECT name FROM people WHERE person_id = ?"),
3453 ("select * from test where !is_good;", "select * from test where ! is_good"),
3455 ("select * from test where ! is_good;", "select * from test where ! is_good"),
3457 ("select * from test where !45;", "select * from test where ! ?"),
3459 ("select * from test where !(select is_good from good_things);", "select * from test where ! ( select is_good from good_things )"),
3461 ("select * from test where !'weird_query'", "select * from test where ! ?"),
3463 ("select * from test where !\"weird_query\"", "select * from test where ! weird_query"),
3465 ("select * from test where !`weird_query`", "select * from test where ! weird_query"),
3467 ("select !- 2", "select ! - ?"),
3469 ("select !+2", "select ! + ?"),
3471 ("select * from test where !- 2", "select * from test where ! - ?"),
3473 ("select count(*) as `count(*)` from test", "select count ( * ) from test"),
3475 ("SELECT age as `age}` FROM profile", "SELECT age FROM profile"),
3477 ("SELECT age as `age``}` FROM profile", "SELECT age FROM profile"),
3479 ("SELECT * from users where user_id =:0_USER", "SELECT * from users where user_id = :0_USER"),
3481 ];
3482 let mut errors = String::new();
3483 for (i, (input, expected)) in cases.iter().enumerate() {
3484 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3485 if got != *expected {
3486 let _ = write!(
3487 errors,
3488 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3489 );
3490 }
3491 }
3492 assert!(errors.is_empty(), "{errors}");
3493 }
3494
3495 #[test]
3500 #[allow(deprecated)]
3501 fn test_suite_all_flags() {
3502 let config = SqlObfuscateConfig {
3503 keep_sql_alias: true,
3504 dollar_quoted_func: true,
3505 keep_null: true,
3506 keep_boolean: true,
3507 keep_positional_parameter: true,
3508 keep_trailing_semicolon: true,
3509 keep_identifier_quotation: true,
3510 replace_bind_parameter: true,
3511 remove_space_between_parentheses: true,
3512 keep_json_path: true,
3513 replace_digits: true,
3514 ..Default::default()
3515 };
3516 let cases: &[(&str, &str)] = &[
3517 ("$2", "?"),
3519 ("(2", "( ?"),
3521 (";ჸ", "ჸ"),
3523 (";ჸ", "ჸ"),
3525 ("@2", "@?"),
3527 ("@C", "@C"),
3529 ("\"\"\"\"", "\""),
3531 ("@ჸ2", "@ჸ?"),
3533 ("\"0\"", "0"),
3535 ];
3536 let mut errors = String::new();
3537 for (i, (input, expected)) in cases.iter().enumerate() {
3538 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3539 if got != *expected {
3540 let _ = write!(
3541 errors,
3542 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3543 );
3544 }
3545 }
3546 assert!(errors.is_empty(), "{errors}");
3547 }
3548
3549 #[test]
3551 #[allow(deprecated)]
3552 fn test_suite_mssql() {
3553 let config = SqlObfuscateConfig::default();
3554 let cases: &[(&str, &str)] = &[
3555 ("\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.*"),
3557 ("select * from ##ThisIsAGlobalTempTable where id = 1", "select * from ##ThisIsAGlobalTempTable where id = ?"),
3559 ("select * from dbo.#ThisIsATempTable where id = 1", "select * from dbo.#ThisIsATempTable where id = ?"),
3561 ("SELECT * from [db_users] where [id] = @1", "SELECT * from db_users where id = @1"),
3563 ];
3564 let mut errors = String::new();
3565 for (i, (input, expected)) in cases.iter().enumerate() {
3566 let got = super::obfuscate_sql(input, &config, DbmsKind::Mssql);
3567 if got != *expected {
3568 let _ = write!(
3569 errors,
3570 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3571 );
3572 }
3573 }
3574 assert!(errors.is_empty(), "{errors}");
3575 }
3576
3577 #[test]
3579 #[allow(deprecated)]
3580 fn test_suite_postgresql() {
3581 let config = SqlObfuscateConfig::default();
3582 let cases: &[(&str, &str)] = &[
3583 (
3585 "select users.custom #> '{a,b}' from users",
3586 "select users.custom #> ? from users",
3587 ),
3588 (
3590 "select users.custom #>> '{a,b}' from users",
3591 "select users.custom #>> ? from users",
3592 ),
3593 (
3595 "select users.custom #- '{a,b}' from users",
3596 "select users.custom #- ? from users",
3597 ),
3598 (
3600 "select users.custom -> 'foo' from users",
3601 "select users.custom -> ? from users",
3602 ),
3603 (
3605 "select users.custom ->> 'foo' from users",
3606 "select users.custom ->> ? from users",
3607 ),
3608 (
3610 "select * from users where user.custom @> '{a,b}'",
3611 "select * from users where user.custom @> ?",
3612 ),
3613 (
3615 "SELECT a FROM foo WHERE value<@name",
3616 "SELECT a FROM foo WHERE value <@ name",
3617 ),
3618 (
3620 "select * from users where user.custom ? 'foo'",
3621 "select * from users where user.custom ? ?",
3622 ),
3623 (
3625 "select * from users where user.custom ?| array [ '1', '2' ]",
3626 "select * from users where user.custom ?| array [ ? ]",
3627 ),
3628 (
3630 "select * from users where user.custom ?& array [ '1', '2' ]",
3631 "select * from users where user.custom ?& array [ ? ]",
3632 ),
3633 ];
3634 let mut errors = String::new();
3635 for (i, (input, expected)) in cases.iter().enumerate() {
3636 let got = super::obfuscate_sql(input, &config, DbmsKind::Postgresql);
3637 if got != *expected {
3638 let _ = write!(
3639 errors,
3640 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3641 );
3642 }
3643 }
3644 assert!(errors.is_empty(), "{errors}");
3645 }
3646
3647 #[test]
3649 fn test_suite_normalize_only() {
3650 let config = SqlObfuscateConfig {
3651 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
3652 ..Default::default()
3653 };
3654 let cases: &[(&str, &str)] = &[
3655 ("SELECT * FROM users WHERE id = 1", "SELECT * FROM users WHERE id = 1"),
3657 ("SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users", "SELECT $func$INSERT INTO table VALUES ( 'a', 1, 2 )$func$ FROM users"),
3659 ("CREATE PROCEDURE TestProc AS BEGIN SELECT * FROM users WHERE id = 1 END", "CREATE PROCEDURE TestProc AS BEGIN SELECT * FROM users WHERE id = 1 END"),
3661 ("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"),
3663 ("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"),
3665 ("WITH users AS (SELECT * FROM people) SELECT * FROM users", "WITH users AS ( SELECT * FROM people ) SELECT * FROM users"),
3667 ];
3668 let mut errors = String::new();
3669 for (i, (input, expected)) in cases.iter().enumerate() {
3670 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3671 if got != *expected {
3672 let _ = write!(
3673 errors,
3674 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3675 );
3676 }
3677 }
3678 assert!(errors.is_empty(), "{errors}");
3679 }
3680
3681 #[test]
3683 fn test_suite_normalize_only_keep_sql_alias() {
3684 let config = SqlObfuscateConfig {
3685 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
3686 keep_sql_alias: true,
3687 ..Default::default()
3688 };
3689 let cases: &[(&str, &str)] = &[
3690 ("\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 )"),
3692 ];
3693 let mut errors = String::new();
3694 for (i, (input, expected)) in cases.iter().enumerate() {
3695 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3696 if got != *expected {
3697 let _ = write!(
3698 errors,
3699 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3700 );
3701 }
3702 }
3703 assert!(errors.is_empty(), "{errors}");
3704 }
3705
3706 #[test]
3708 fn test_suite_normalize_only_remove_space_between_parentheses() {
3709 let config = SqlObfuscateConfig {
3710 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
3711 remove_space_between_parentheses: true,
3712 ..Default::default()
3713 };
3714 let cases: &[(&str, &str)] = &[
3715 (
3717 "SELECT * FROM users WHERE id = 1 AND (name = 'test' OR name = 'test2')",
3718 "SELECT * FROM users WHERE id = 1 AND (name = 'test' OR name = 'test2')",
3719 ),
3720 ];
3721 let mut errors = String::new();
3722 for (i, (input, expected)) in cases.iter().enumerate() {
3723 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3724 if got != *expected {
3725 let _ = write!(
3726 errors,
3727 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3728 );
3729 }
3730 }
3731 assert!(errors.is_empty(), "{errors}");
3732 }
3733
3734 #[test]
3736 fn test_suite_normalize_only_keep_trailing_semicolon() {
3737 let config = SqlObfuscateConfig {
3738 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
3739 keep_trailing_semicolon: true,
3740 ..Default::default()
3741 };
3742 let cases: &[(&str, &str)] = &[
3743 (
3745 "SELECT * FROM users WHERE id = 1 AND name = 'test';",
3746 "SELECT * FROM users WHERE id = 1 AND name = 'test';",
3747 ),
3748 ];
3749 let mut errors = String::new();
3750 for (i, (input, expected)) in cases.iter().enumerate() {
3751 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3752 if got != *expected {
3753 let _ = write!(
3754 errors,
3755 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3756 );
3757 }
3758 }
3759 assert!(errors.is_empty(), "{errors}");
3760 }
3761
3762 #[test]
3764 fn test_suite_normalize_only_keep_identifier_quotation() {
3765 let config = SqlObfuscateConfig {
3766 obfuscation_mode: SqlObfuscationMode::NormalizeOnly,
3767 keep_identifier_quotation: true,
3768 ..Default::default()
3769 };
3770 let cases: &[(&str, &str)] = &[
3771 (
3773 "SELECT * FROM \"users\" WHERE id = 1 AND name = 'test'",
3774 "SELECT * FROM \"users\" WHERE id = 1 AND name = 'test'",
3775 ),
3776 ];
3777 let mut errors = String::new();
3778 for (i, (input, expected)) in cases.iter().enumerate() {
3779 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3780 if got != *expected {
3781 let _ = write!(
3782 errors,
3783 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3784 );
3785 }
3786 }
3787 assert!(errors.is_empty(), "{errors}");
3788 }
3789
3790 #[test]
3792 fn test_suite_obfuscate_and_normalize() {
3793 let config = SqlObfuscateConfig {
3794 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3795 ..Default::default()
3796 };
3797 let cases: &[(&str, &str)] = &[
3798 ("SELECT * FROM users WHERE id = 1", "SELECT * FROM users WHERE id = ?"),
3800 ("SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users", "SELECT ? FROM users"),
3802 ("CREATE PROCEDURE TestProc AS BEGIN SELECT * FROM users WHERE id = 1 END", "CREATE PROCEDURE TestProc AS BEGIN SELECT * FROM users WHERE id = ? END"),
3804 ("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"),
3806 ("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 ?"),
3808 ("CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(255))", "CREATE TABLE IF NOT EXISTS users ( id INT, name VARCHAR ( ? ) )"),
3810 ("SELECT * FROM users WHERE id = @P1 AND name = @P2", "SELECT * FROM users WHERE id = @P1 AND name = @P2"),
3812 ("SELECT * FROM ONLY users WHERE id = 1", "SELECT * FROM ONLY users WHERE id = ?"),
3814 ("WITH users AS (SELECT * FROM people) SELECT * FROM users where id = 1", "WITH users AS ( SELECT * FROM people ) SELECT * FROM users where id = ?"),
3816 ("SELECT * FROM users WHERE id = 1 AND name->'first' = 'test'", "SELECT * FROM users WHERE id = ? AND name -> ? = ?"),
3818 ];
3819 let mut errors = String::new();
3820 for (i, (input, expected)) in cases.iter().enumerate() {
3821 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3822 if got != *expected {
3823 let _ = write!(
3824 errors,
3825 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3826 );
3827 }
3828 }
3829 assert!(errors.is_empty(), "{errors}");
3830 }
3831
3832 #[test]
3834 fn test_suite_obfuscate_and_normalize_replace_digits() {
3835 let config = SqlObfuscateConfig {
3836 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3837 replace_digits: true,
3838 ..Default::default()
3839 };
3840 let cases: &[(&str, &str)] = &[
3841 (
3843 "SELECT * FROM users123 WHERE id = 1",
3844 "SELECT * FROM users? WHERE id = ?",
3845 ),
3846 ];
3847 let mut errors = String::new();
3848 for (i, (input, expected)) in cases.iter().enumerate() {
3849 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3850 if got != *expected {
3851 let _ = write!(
3852 errors,
3853 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3854 );
3855 }
3856 }
3857 assert!(errors.is_empty(), "{errors}");
3858 }
3859
3860 #[test]
3862 fn test_suite_obfuscate_and_normalize_keep_sql_alias() {
3863 let config = SqlObfuscateConfig {
3864 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3865 keep_sql_alias: true,
3866 ..Default::default()
3867 };
3868 let cases: &[(&str, &str)] = &[
3869 ("\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 ( ? )"),
3871 ];
3872 let mut errors = String::new();
3873 for (i, (input, expected)) in cases.iter().enumerate() {
3874 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3875 if got != *expected {
3876 let _ = write!(
3877 errors,
3878 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3879 );
3880 }
3881 }
3882 assert!(errors.is_empty(), "{errors}");
3883 }
3884
3885 #[test]
3887 fn test_suite_obfuscate_and_normalize_dollar_quoted_func() {
3888 let config = SqlObfuscateConfig {
3889 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3890 dollar_quoted_func: true,
3891 ..Default::default()
3892 };
3893 let cases: &[(&str, &str)] = &[
3894 (
3896 "SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users",
3897 "SELECT $func$INSERT INTO table VALUES ( ? )$func$ FROM users",
3898 ),
3899 ];
3900 let mut errors = String::new();
3901 for (i, (input, expected)) in cases.iter().enumerate() {
3902 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3903 if got != *expected {
3904 let _ = write!(
3905 errors,
3906 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3907 );
3908 }
3909 }
3910 assert!(errors.is_empty(), "{errors}");
3911 }
3912
3913 #[test]
3915 fn test_suite_obfuscate_and_normalize_dollar_quoted_func_replace_digits() {
3916 let config = SqlObfuscateConfig {
3917 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3918 dollar_quoted_func: true,
3919 replace_digits: true,
3920 ..Default::default()
3921 };
3922 let cases: &[(&str, &str)] = &[
3923 (
3925 "SELECT * FROM users123 WHERE id = $tag$1$tag$",
3926 "SELECT * FROM users? WHERE id = ?",
3927 ),
3928 ];
3929 let mut errors = String::new();
3930 for (i, (input, expected)) in cases.iter().enumerate() {
3931 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3932 if got != *expected {
3933 let _ = write!(
3934 errors,
3935 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3936 );
3937 }
3938 }
3939 assert!(errors.is_empty(), "{errors}");
3940 }
3941
3942 #[test]
3944 fn test_suite_obfuscate_and_normalize_remove_space_between_parentheses() {
3945 let config = SqlObfuscateConfig {
3946 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3947 remove_space_between_parentheses: true,
3948 ..Default::default()
3949 };
3950 let cases: &[(&str, &str)] = &[
3951 (
3953 "SELECT * FROM users WHERE id = 1 AND (name = 'test' OR name = 'test2')",
3954 "SELECT * FROM users WHERE id = ? AND (name = ? OR name = ?)",
3955 ),
3956 ];
3957 let mut errors = String::new();
3958 for (i, (input, expected)) in cases.iter().enumerate() {
3959 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3960 if got != *expected {
3961 let _ = write!(
3962 errors,
3963 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3964 );
3965 }
3966 }
3967 assert!(errors.is_empty(), "{errors}");
3968 }
3969
3970 #[test]
3972 fn test_suite_obfuscate_and_normalize_keep_null() {
3973 let config = SqlObfuscateConfig {
3974 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
3975 keep_null: true,
3976 ..Default::default()
3977 };
3978 let cases: &[(&str, &str)] = &[
3979 (
3981 "SELECT * FROM users WHERE id = 1 AND name IS NULL",
3982 "SELECT * FROM users WHERE id = ? AND name IS NULL",
3983 ),
3984 ];
3985 let mut errors = String::new();
3986 for (i, (input, expected)) in cases.iter().enumerate() {
3987 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
3988 if got != *expected {
3989 let _ = write!(
3990 errors,
3991 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
3992 );
3993 }
3994 }
3995 assert!(errors.is_empty(), "{errors}");
3996 }
3997
3998 #[test]
4000 fn test_suite_obfuscate_and_normalize_keep_boolean() {
4001 let config = SqlObfuscateConfig {
4002 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
4003 keep_boolean: true,
4004 ..Default::default()
4005 };
4006 let cases: &[(&str, &str)] = &[
4007 (
4009 "SELECT * FROM users WHERE id = 1 AND name is TRUE",
4010 "SELECT * FROM users WHERE id = ? AND name is TRUE",
4011 ),
4012 ];
4013 let mut errors = String::new();
4014 for (i, (input, expected)) in cases.iter().enumerate() {
4015 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4016 if got != *expected {
4017 let _ = write!(
4018 errors,
4019 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4020 );
4021 }
4022 }
4023 assert!(errors.is_empty(), "{errors}");
4024 }
4025
4026 #[test]
4028 fn test_suite_obfuscate_and_normalize_keep_positional_parameter() {
4029 let config = SqlObfuscateConfig {
4030 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
4031 keep_positional_parameter: true,
4032 ..Default::default()
4033 };
4034 let cases: &[(&str, &str)] = &[
4035 (
4037 "SELECT * FROM users WHERE id = 1 AND name = $1 and id = $2",
4038 "SELECT * FROM users WHERE id = ? AND name = $1 and id = $2",
4039 ),
4040 ];
4041 let mut errors = String::new();
4042 for (i, (input, expected)) in cases.iter().enumerate() {
4043 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4044 if got != *expected {
4045 let _ = write!(
4046 errors,
4047 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4048 );
4049 }
4050 }
4051 assert!(errors.is_empty(), "{errors}");
4052 }
4053
4054 #[test]
4056 fn test_suite_obfuscate_and_normalize_keep_trailing_semicolon() {
4057 let config = SqlObfuscateConfig {
4058 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
4059 keep_trailing_semicolon: true,
4060 ..Default::default()
4061 };
4062 let cases: &[(&str, &str)] = &[
4063 (
4065 "SELECT * FROM users WHERE id = 1 AND name = 'test';",
4066 "SELECT * FROM users WHERE id = ? AND name = ?;",
4067 ),
4068 ];
4069 let mut errors = String::new();
4070 for (i, (input, expected)) in cases.iter().enumerate() {
4071 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4072 if got != *expected {
4073 let _ = write!(
4074 errors,
4075 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4076 );
4077 }
4078 }
4079 assert!(errors.is_empty(), "{errors}");
4080 }
4081
4082 #[test]
4084 fn test_suite_obfuscate_and_normalize_keep_identifier_quotation() {
4085 let config = SqlObfuscateConfig {
4086 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
4087 keep_identifier_quotation: true,
4088 ..Default::default()
4089 };
4090 let cases: &[(&str, &str)] = &[
4091 (
4093 "SELECT * FROM \"users\" WHERE id = 1 AND name = 'test'",
4094 "SELECT * FROM \"users\" WHERE id = ? AND name = ?",
4095 ),
4096 ];
4097 let mut errors = String::new();
4098 for (i, (input, expected)) in cases.iter().enumerate() {
4099 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4100 if got != *expected {
4101 let _ = write!(
4102 errors,
4103 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4104 );
4105 }
4106 }
4107 assert!(errors.is_empty(), "{errors}");
4108 }
4109
4110 #[test]
4112 fn test_suite_obfuscate_and_normalize_replace_bind_parameter() {
4113 let config = SqlObfuscateConfig {
4114 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
4115 replace_bind_parameter: true,
4116 ..Default::default()
4117 };
4118 let cases: &[(&str, &str)] = &[
4119 (
4121 "SELECT * FROM users WHERE id = @P1 AND name = @P2",
4122 "SELECT * FROM users WHERE id = ? AND name = ?",
4123 ),
4124 ];
4125 let mut errors = String::new();
4126 for (i, (input, expected)) in cases.iter().enumerate() {
4127 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4128 if got != *expected {
4129 let _ = write!(
4130 errors,
4131 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4132 );
4133 }
4134 }
4135 assert!(errors.is_empty(), "{errors}");
4136 }
4137
4138 #[test]
4140 fn test_suite_obfuscate_and_normalize_keep_json_path() {
4141 let config = SqlObfuscateConfig {
4142 obfuscation_mode: SqlObfuscationMode::ObfuscateAndNormalize,
4143 keep_json_path: true,
4144 ..Default::default()
4145 };
4146 let cases: &[(&str, &str)] = &[
4147 (
4149 "SELECT * FROM users WHERE id = 1 AND name->'first' = 'test'",
4150 "SELECT * FROM users WHERE id = ? AND name -> 'first' = ?",
4151 ),
4152 (
4154 "SELECT * FROM users WHERE id = 1 AND name->>2 = 'test'",
4155 "SELECT * FROM users WHERE id = ? AND name ->> 2 = ?",
4156 ),
4157 ];
4158 let mut errors = String::new();
4159 for (i, (input, expected)) in cases.iter().enumerate() {
4160 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4161 if got != *expected {
4162 let _ = write!(
4163 errors,
4164 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4165 );
4166 }
4167 }
4168 assert!(errors.is_empty(), "{errors}");
4169 }
4170
4171 #[test]
4173 fn test_suite_obfuscate_only() {
4174 let config = SqlObfuscateConfig {
4175 obfuscation_mode: SqlObfuscationMode::ObfuscateOnly,
4176 ..Default::default()
4177 };
4178 let cases: &[(&str, &str)] = &[
4179 ("SELECT * FROM users WHERE id = 1", "SELECT * FROM users WHERE id = ?"),
4181 ("SELECT \"table\".\"field\" FROM \"table\" WHERE \"table\".\"otherfield\" = $? AND \"table\".\"thirdfield\" = $?;", "SELECT \"table\".\"field\" FROM \"table\" WHERE \"table\".\"otherfield\" = $? AND \"table\".\"thirdfield\" = $?;"),
4183 ("SELECT * FROM users123 WHERE id = 1", "SELECT * FROM users123 WHERE id = ?"),
4185 ("SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users", "SELECT ? FROM users"),
4187 ];
4188 let mut errors = String::new();
4189 for (i, (input, expected)) in cases.iter().enumerate() {
4190 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4191 if got != *expected {
4192 let _ = write!(
4193 errors,
4194 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4195 );
4196 }
4197 }
4198 assert!(errors.is_empty(), "{errors}");
4199 }
4200
4201 #[test]
4203 fn test_suite_obfuscate_only_replace_digits() {
4204 let config = SqlObfuscateConfig {
4205 obfuscation_mode: SqlObfuscationMode::ObfuscateOnly,
4206 replace_digits: true,
4207 ..Default::default()
4208 };
4209 let cases: &[(&str, &str)] = &[
4210 (
4212 "SELECT * FROM users123 WHERE id = 1",
4213 "SELECT * FROM users? WHERE id = ?",
4214 ),
4215 ];
4216 let mut errors = String::new();
4217 for (i, (input, expected)) in cases.iter().enumerate() {
4218 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4219 if got != *expected {
4220 let _ = write!(
4221 errors,
4222 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4223 );
4224 }
4225 }
4226 assert!(errors.is_empty(), "{errors}");
4227 }
4228
4229 #[test]
4231 fn test_suite_obfuscate_only_dollar_quoted_func() {
4232 let config = SqlObfuscateConfig {
4233 obfuscation_mode: SqlObfuscationMode::ObfuscateOnly,
4234 dollar_quoted_func: true,
4235 ..Default::default()
4236 };
4237 let cases: &[(&str, &str)] = &[
4238 (
4240 "SELECT $func$INSERT INTO table VALUES ('a', 1, 2)$func$ FROM users",
4241 "SELECT $func$INSERT INTO table VALUES (?, ?, ?)$func$ FROM users",
4242 ),
4243 ];
4244 let mut errors = String::new();
4245 for (i, (input, expected)) in cases.iter().enumerate() {
4246 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4247 if got != *expected {
4248 let _ = write!(
4249 errors,
4250 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4251 );
4252 }
4253 }
4254 assert!(errors.is_empty(), "{errors}");
4255 }
4256
4257 #[test]
4259 fn test_suite_obfuscate_only_dollar_quoted_func_replace_digits() {
4260 let config = SqlObfuscateConfig {
4261 obfuscation_mode: SqlObfuscationMode::ObfuscateOnly,
4262 dollar_quoted_func: true,
4263 replace_digits: true,
4264 ..Default::default()
4265 };
4266 let cases: &[(&str, &str)] = &[
4267 (
4269 "SELECT * FROM users123 WHERE id = $tag$1$tag$",
4270 "SELECT * FROM users? WHERE id = ?",
4271 ),
4272 ];
4273 let mut errors = String::new();
4274 for (i, (input, expected)) in cases.iter().enumerate() {
4275 let got = super::obfuscate_sql(input, &config, DbmsKind::Generic);
4276 if got != *expected {
4277 let _ = write!(
4278 errors,
4279 "case {i} ({input:?}):\n expected {expected:?}\n got {got:?}\n"
4280 );
4281 }
4282 }
4283 assert!(errors.is_empty(), "{errors}");
4284 }
4285
4286 #[test]
4290 fn test_collapse_limit_case_insensitive() {
4291 #[allow(deprecated)]
4292 let config = SqlObfuscateConfig {
4293 obfuscation_mode: SqlObfuscationMode::Unspecified,
4294 ..Default::default()
4295 };
4296 let got_upper =
4297 super::obfuscate_sql("SELECT * FROM t LIMIT 5, 10", &config, DbmsKind::Generic);
4298 assert_eq!(
4299 got_upper, "SELECT * FROM t LIMIT ?",
4300 "uppercase LIMIT should be collapsed: {got_upper:?}"
4301 );
4302 let got_lower =
4304 super::obfuscate_sql("SELECT * FROM t limit 5, 10", &config, DbmsKind::Generic);
4305 assert_eq!(
4306 got_lower, "SELECT * FROM t limit ?",
4307 "lowercase limit should also be collapsed: {got_lower:?}"
4308 );
4309 }
4310}