1#![allow(clippy::derivable_impls)]
4
5mod config;
6mod operators;
7mod strings;
8pub mod types;
9
10pub use config::MySqlModeFlags;
11pub use operators::{ConcatOperator, DivisionBehavior, OperatorBehavior};
13pub use strings::{Collation, StringBehavior};
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38pub enum SqlMode {
39 MySQL { flags: MySqlModeFlags },
44
45 SQLite,
53}
54
55impl Default for SqlMode {
56 fn default() -> Self {
57 SqlMode::MySQL { flags: MySqlModeFlags::default() }
62 }
63}
64
65impl std::fmt::Display for SqlMode {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 SqlMode::MySQL { .. } => write!(f, "mysql"),
69 SqlMode::SQLite => write!(f, "sqlite"),
70 }
71 }
72}
73
74impl std::str::FromStr for SqlMode {
75 type Err = String;
76
77 fn from_str(s: &str) -> Result<Self, Self::Err> {
78 match s.to_lowercase().as_str() {
80 "mysql" => return Ok(SqlMode::MySQL { flags: MySqlModeFlags::default() }),
81 "mysql_slt" => {
86 return Ok(SqlMode::MySQL {
87 flags: MySqlModeFlags::with_sqlite_division_semantics(),
88 })
89 }
90 "sqlite" => return Ok(SqlMode::SQLite),
91 _ => {}
92 }
93
94 parse_mysql_mode_flags(s)
98 }
99}
100
101fn parse_mysql_mode_flags(s: &str) -> Result<SqlMode, String> {
120 let mut flags = MySqlModeFlags::default();
121
122 for mode in s.split(',') {
124 let mode = mode.trim();
126 if mode.is_empty() {
127 continue;
128 }
129
130 match mode.to_uppercase().as_str() {
132 "STRICT_TRANS_TABLES" | "STRICT_ALL_TABLES" => {
134 flags.strict_mode = true;
135 }
136 "PIPES_AS_CONCAT" => {
137 flags.pipes_as_concat = true;
138 }
139 "ANSI_QUOTES" => {
140 flags.ansi_quotes = true;
141 }
142 "ANSI" => {
143 flags.pipes_as_concat = true;
145 flags.ansi_quotes = true;
146 }
147
148 "NO_ZERO_IN_DATE"
151 | "NO_ZERO_DATE"
152 | "ERROR_FOR_DIVISION_BY_ZERO"
153 | "NO_ENGINE_SUBSTITUTION"
154 | "ONLY_FULL_GROUP_BY"
155 | "NO_AUTO_CREATE_USER"
156 | "NO_AUTO_VALUE_ON_ZERO"
157 | "NO_BACKSLASH_ESCAPES"
158 | "NO_DIR_IN_CREATE"
159 | "NO_UNSIGNED_SUBTRACTION"
160 | "PAD_CHAR_TO_FULL_LENGTH"
161 | "REAL_AS_FLOAT"
162 | "TIME_TRUNCATE_FRACTIONAL"
163 | "IGNORE_SPACE"
164 | "TRADITIONAL"
165 | "ALLOW_INVALID_DATES"
166 | "HIGH_NOT_PRECEDENCE" => {
167 }
169
170 other => {
173 if other.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
175 } else {
178 return Err(format!(
179 "Unknown SQL mode: '{}'. Valid modes: mysql, mysql_slt, sqlite, or MySQL mode flags",
180 s
181 ));
182 }
183 }
184 }
185 }
186
187 Ok(SqlMode::MySQL { flags })
188}
189
190impl SqlMode {
191 pub fn mysql_flags(&self) -> Option<&MySqlModeFlags> {
193 match self {
194 SqlMode::MySQL { flags } => Some(flags),
195 SqlMode::SQLite => None,
196 }
197 }
198}
199
200#[allow(dead_code)]
202const MYSQL_SUPPORTED_COLLATIONS: &[Collation] =
203 &[Collation::Binary, Collation::Utf8Binary, Collation::Utf8GeneralCi];
204
205#[allow(dead_code)]
206const SQLITE_SUPPORTED_COLLATIONS: &[Collation] =
207 &[Collation::Binary, Collation::NoCase, Collation::Rtrim];
208
209impl StringBehavior for SqlMode {
210 fn default_string_comparison_case_sensitive(&self) -> bool {
211 match self {
212 SqlMode::MySQL { .. } => false, SqlMode::SQLite => true, }
215 }
216
217 fn default_collation(&self) -> Collation {
218 match self {
219 SqlMode::MySQL { .. } => Collation::Utf8GeneralCi, SqlMode::SQLite => Collation::Binary, }
222 }
223
224 fn supported_collations(&self) -> &[Collation] {
225 match self {
226 SqlMode::MySQL { .. } => MYSQL_SUPPORTED_COLLATIONS,
227 SqlMode::SQLite => SQLITE_SUPPORTED_COLLATIONS,
228 }
229 }
230}
231
232impl OperatorBehavior for SqlMode {
233 fn integer_division_behavior(&self) -> DivisionBehavior {
234 match self {
235 SqlMode::MySQL { .. } => DivisionBehavior::Decimal,
236 SqlMode::SQLite => DivisionBehavior::Integer,
237 }
238 }
239
240 fn supports_xor(&self) -> bool {
241 match self {
242 SqlMode::MySQL { .. } => true,
243 SqlMode::SQLite => false,
244 }
245 }
246
247 fn supports_integer_div_operator(&self) -> bool {
248 match self {
249 SqlMode::MySQL { .. } => true,
250 SqlMode::SQLite => false,
251 }
252 }
253
254 fn string_concat_operator(&self) -> ConcatOperator {
255 match self {
256 SqlMode::MySQL { .. } => ConcatOperator::Function,
257 SqlMode::SQLite => ConcatOperator::PipePipe,
258 }
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_default_is_mysql() {
268 let mode = SqlMode::default();
270 assert!(matches!(mode, SqlMode::MySQL { .. }));
271 }
272
273 #[test]
274 fn test_mysql_mode_has_default_flags() {
275 let mode = SqlMode::MySQL { flags: MySqlModeFlags::default() };
277 if let SqlMode::MySQL { flags } = mode {
278 assert_eq!(flags, MySqlModeFlags::default());
279 } else {
280 panic!("Expected MySQL mode");
281 }
282 }
283
284 #[test]
285 fn test_division_behavior() {
286 use crate::{
287 sql_mode::types::{TypeBehavior, ValueType},
288 SqlValue,
289 };
290
291 let mysql_mode = SqlMode::MySQL { flags: MySqlModeFlags::default() };
292 assert_eq!(
294 mysql_mode.division_result_type(&SqlValue::Integer(5), &SqlValue::Integer(2)),
295 ValueType::Numeric
296 );
297
298 let sqlite_mode = SqlMode::SQLite;
299 assert_eq!(
301 sqlite_mode.division_result_type(&SqlValue::Integer(5), &SqlValue::Integer(2)),
302 ValueType::Integer
303 );
304 }
305
306 #[test]
307 fn test_mysql_flags_accessor() {
308 let mysql_mode = SqlMode::MySQL { flags: MySqlModeFlags::with_pipes_as_concat() };
309 assert!(mysql_mode.mysql_flags().is_some());
310 assert!(mysql_mode.mysql_flags().unwrap().pipes_as_concat);
311
312 let sqlite_mode = SqlMode::SQLite;
313 assert!(sqlite_mode.mysql_flags().is_none());
314 }
315
316 #[test]
317 fn test_sqlmode_with_flags() {
318 let mode = SqlMode::MySQL {
319 flags: MySqlModeFlags { pipes_as_concat: true, ..Default::default() },
320 };
321
322 match mode {
323 SqlMode::MySQL { flags } => {
324 assert!(flags.pipes_as_concat);
325 assert!(!flags.ansi_quotes);
326 assert!(!flags.strict_mode);
327 }
328 _ => panic!("Expected MySQL mode"),
329 }
330 }
331
332 #[test]
333 fn test_sqlmode_with_custom_flags() {
334 let mode = SqlMode::MySQL { flags: MySqlModeFlags::ansi() };
335
336 match mode {
337 SqlMode::MySQL { flags } => {
338 assert!(flags.pipes_as_concat);
339 assert!(flags.ansi_quotes);
340 assert!(!flags.strict_mode);
341 }
342 _ => panic!("Expected MySQL mode"),
343 }
344 }
345
346 #[test]
347 fn test_mysql_string_comparison() {
348 let mode = SqlMode::MySQL { flags: MySqlModeFlags::default() };
349 assert!(!mode.default_string_comparison_case_sensitive());
350 assert_eq!(mode.default_collation(), Collation::Utf8GeneralCi);
351 }
352
353 #[test]
354 fn test_sqlite_string_comparison() {
355 let mode = SqlMode::SQLite;
356 assert!(mode.default_string_comparison_case_sensitive());
357 assert_eq!(mode.default_collation(), Collation::Binary);
358 }
359
360 #[test]
361 fn test_mysql_supported_collations() {
362 let mode = SqlMode::MySQL { flags: MySqlModeFlags::default() };
363 let collations = mode.supported_collations();
364 assert_eq!(collations.len(), 3);
365 assert!(collations.contains(&Collation::Binary));
366 assert!(collations.contains(&Collation::Utf8Binary));
367 assert!(collations.contains(&Collation::Utf8GeneralCi));
368 }
369
370 #[test]
371 fn test_sqlite_supported_collations() {
372 let mode = SqlMode::SQLite;
373 let collations = mode.supported_collations();
374 assert_eq!(collations.len(), 3);
375 assert!(collations.contains(&Collation::Binary));
376 assert!(collations.contains(&Collation::NoCase));
377 assert!(collations.contains(&Collation::Rtrim));
378 }
379
380 #[test]
381 fn test_collation_case_sensitivity_consistency() {
382 let mysql_mode = SqlMode::MySQL { flags: MySqlModeFlags::default() };
384 assert!(!mysql_mode.default_string_comparison_case_sensitive());
385 assert_eq!(mysql_mode.default_collation(), Collation::Utf8GeneralCi);
386
387 let sqlite_mode = SqlMode::SQLite;
389 assert!(sqlite_mode.default_string_comparison_case_sensitive());
390 assert_eq!(sqlite_mode.default_collation(), Collation::Binary);
391 }
392
393 #[test]
396 fn test_integer_division_behavior() {
397 let mysql_mode = SqlMode::MySQL { flags: MySqlModeFlags::default() };
398 assert_eq!(mysql_mode.integer_division_behavior(), DivisionBehavior::Decimal);
399
400 let sqlite_mode = SqlMode::SQLite;
401 assert_eq!(sqlite_mode.integer_division_behavior(), DivisionBehavior::Integer);
402 }
403
404 #[test]
405 fn test_xor_support() {
406 let mysql_mode = SqlMode::MySQL { flags: MySqlModeFlags::default() };
407 assert!(mysql_mode.supports_xor());
408
409 let sqlite_mode = SqlMode::SQLite;
410 assert!(!sqlite_mode.supports_xor());
411 }
412
413 #[test]
414 fn test_integer_div_operator_support() {
415 let mysql_mode = SqlMode::MySQL { flags: MySqlModeFlags::default() };
416 assert!(mysql_mode.supports_integer_div_operator());
417
418 let sqlite_mode = SqlMode::SQLite;
419 assert!(!sqlite_mode.supports_integer_div_operator());
420 }
421
422 #[test]
423 fn test_string_concat_operator() {
424 let mysql_mode = SqlMode::MySQL { flags: MySqlModeFlags::default() };
425 assert_eq!(mysql_mode.string_concat_operator(), ConcatOperator::Function);
426
427 let sqlite_mode = SqlMode::SQLite;
428 assert_eq!(sqlite_mode.string_concat_operator(), ConcatOperator::PipePipe);
429 }
430
431 #[test]
434 fn test_display_mysql() {
435 let mode = SqlMode::MySQL { flags: MySqlModeFlags::default() };
436 assert_eq!(mode.to_string(), "mysql");
437 }
438
439 #[test]
440 fn test_display_sqlite() {
441 let mode = SqlMode::SQLite;
442 assert_eq!(mode.to_string(), "sqlite");
443 }
444
445 #[test]
446 fn test_from_str_mysql() {
447 let mode: SqlMode = "mysql".parse().unwrap();
448 assert!(matches!(mode, SqlMode::MySQL { .. }));
449 }
450
451 #[test]
452 fn test_from_str_sqlite() {
453 let mode: SqlMode = "sqlite".parse().unwrap();
454 assert!(matches!(mode, SqlMode::SQLite));
455 }
456
457 #[test]
458 fn test_from_str_case_insensitive() {
459 let mode1: SqlMode = "MYSQL".parse().unwrap();
460 assert!(matches!(mode1, SqlMode::MySQL { .. }));
461
462 let mode2: SqlMode = "SQLite".parse().unwrap();
463 assert!(matches!(mode2, SqlMode::SQLite));
464
465 let mode3: SqlMode = "MySql".parse().unwrap();
466 assert!(matches!(mode3, SqlMode::MySQL { .. }));
467 }
468
469 #[test]
470 fn test_from_str_mysql_slt() {
471 let mode: SqlMode = "mysql_slt".parse().unwrap();
473 match mode {
474 SqlMode::MySQL { flags } => {
475 assert!(flags.sqlite_division_semantics);
476 assert!(!flags.pipes_as_concat);
477 assert!(!flags.ansi_quotes);
478 assert!(!flags.strict_mode);
479 }
480 _ => panic!("Expected MySQL mode"),
481 }
482 }
483
484 #[test]
485 fn test_from_str_invalid() {
486 let result: Result<SqlMode, _> = "invalid!@#".parse();
488 assert!(result.is_err());
489 let err = result.unwrap_err();
490 assert!(err.contains("Unknown SQL mode"));
491 }
492
493 #[test]
496 fn test_from_str_mysql_mode_flags() {
497 let mode: SqlMode =
499 "ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
500 .parse()
501 .unwrap();
502 match mode {
503 SqlMode::MySQL { flags } => {
504 assert!(flags.strict_mode);
505 assert!(!flags.pipes_as_concat);
506 assert!(!flags.ansi_quotes);
507 }
508 _ => panic!("Expected MySQL mode"),
509 }
510 }
511
512 #[test]
513 fn test_from_str_leading_comma() {
514 let mode: SqlMode =
516 ",STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
517 .parse()
518 .unwrap();
519 match mode {
520 SqlMode::MySQL { flags } => {
521 assert!(flags.strict_mode);
522 }
523 _ => panic!("Expected MySQL mode"),
524 }
525 }
526
527 #[test]
528 fn test_from_str_trailing_comma() {
529 let mode: SqlMode = "STRICT_TRANS_TABLES,".parse().unwrap();
531 match mode {
532 SqlMode::MySQL { flags } => {
533 assert!(flags.strict_mode);
534 }
535 _ => panic!("Expected MySQL mode"),
536 }
537 }
538
539 #[test]
540 fn test_from_str_empty_segments() {
541 let mode: SqlMode = "STRICT_TRANS_TABLES,,PIPES_AS_CONCAT".parse().unwrap();
543 match mode {
544 SqlMode::MySQL { flags } => {
545 assert!(flags.strict_mode);
546 assert!(flags.pipes_as_concat);
547 }
548 _ => panic!("Expected MySQL mode"),
549 }
550 }
551
552 #[test]
553 fn test_from_str_empty_string() {
554 let mode: SqlMode = "".parse().unwrap();
556 match mode {
557 SqlMode::MySQL { flags } => {
558 assert!(!flags.strict_mode);
559 assert!(!flags.pipes_as_concat);
560 assert!(!flags.ansi_quotes);
561 }
562 _ => panic!("Expected MySQL mode"),
563 }
564 }
565
566 #[test]
567 fn test_from_str_only_commas() {
568 let mode: SqlMode = ",,,".parse().unwrap();
570 match mode {
571 SqlMode::MySQL { flags } => {
572 assert!(!flags.strict_mode);
573 assert!(!flags.pipes_as_concat);
574 assert!(!flags.ansi_quotes);
575 }
576 _ => panic!("Expected MySQL mode"),
577 }
578 }
579
580 #[test]
581 fn test_from_str_ansi_mode() {
582 let mode: SqlMode = "ANSI".parse().unwrap();
584 match mode {
585 SqlMode::MySQL { flags } => {
586 assert!(flags.pipes_as_concat);
587 assert!(flags.ansi_quotes);
588 }
589 _ => panic!("Expected MySQL mode"),
590 }
591 }
592
593 #[test]
594 fn test_from_str_pipes_as_concat() {
595 let mode: SqlMode = "PIPES_AS_CONCAT".parse().unwrap();
596 match mode {
597 SqlMode::MySQL { flags } => {
598 assert!(flags.pipes_as_concat);
599 assert!(!flags.ansi_quotes);
600 }
601 _ => panic!("Expected MySQL mode"),
602 }
603 }
604
605 #[test]
606 fn test_from_str_ansi_quotes() {
607 let mode: SqlMode = "ANSI_QUOTES".parse().unwrap();
608 match mode {
609 SqlMode::MySQL { flags } => {
610 assert!(!flags.pipes_as_concat);
611 assert!(flags.ansi_quotes);
612 }
613 _ => panic!("Expected MySQL mode"),
614 }
615 }
616
617 #[test]
618 fn test_from_str_mode_flags_case_insensitive() {
619 let mode: SqlMode = "strict_trans_tables,pipes_as_concat".parse().unwrap();
621 match mode {
622 SqlMode::MySQL { flags } => {
623 assert!(flags.strict_mode);
624 assert!(flags.pipes_as_concat);
625 }
626 _ => panic!("Expected MySQL mode"),
627 }
628 }
629
630 #[test]
631 fn test_from_str_mode_flags_with_whitespace() {
632 let mode: SqlMode = " STRICT_TRANS_TABLES , PIPES_AS_CONCAT ".parse().unwrap();
634 match mode {
635 SqlMode::MySQL { flags } => {
636 assert!(flags.strict_mode);
637 assert!(flags.pipes_as_concat);
638 }
639 _ => panic!("Expected MySQL mode"),
640 }
641 }
642
643 #[test]
644 fn test_from_str_unknown_mode_accepted() {
645 let mode: SqlMode = "SOME_FUTURE_MODE,STRICT_TRANS_TABLES".parse().unwrap();
647 match mode {
648 SqlMode::MySQL { flags } => {
649 assert!(flags.strict_mode);
650 }
651 _ => panic!("Expected MySQL mode"),
652 }
653 }
654
655 #[test]
656 fn test_roundtrip() {
657 let mysql = SqlMode::MySQL { flags: MySqlModeFlags::default() };
659 let mysql_str = mysql.to_string();
660 let mysql_parsed: SqlMode = mysql_str.parse().unwrap();
661 assert!(matches!(mysql_parsed, SqlMode::MySQL { .. }));
662
663 let sqlite = SqlMode::SQLite;
665 let sqlite_str = sqlite.to_string();
666 let sqlite_parsed: SqlMode = sqlite_str.parse().unwrap();
667 assert!(matches!(sqlite_parsed, SqlMode::SQLite));
668 }
669}