Skip to main content

yang_db/mysql/
condition.rs

1use chrono::NaiveDateTime;
2use serde_json::Value as JsonValue;
3
4/// SQL 值类型
5#[derive(Debug, Clone)]
6pub enum SqlValue {
7    Null,
8    Bool(bool),
9    Int(i64),
10    Float(f64),
11    String(String),
12    Bytes(Vec<u8>),
13    Json(JsonValue),
14    DateTime(NaiveDateTime),
15    Timestamp(i64),
16}
17
18/// 查询条件
19#[derive(Debug, Clone)]
20pub enum Condition {
21    /// 相等
22    Eq(String, SqlValue),
23    /// 不等
24    Ne(String, SqlValue),
25    /// 大于
26    Gt(String, SqlValue),
27    /// 小于
28    Lt(String, SqlValue),
29    /// 大于等于
30    Gte(String, SqlValue),
31    /// 小于等于
32    Lte(String, SqlValue),
33    /// IN 条件
34    In(String, Vec<SqlValue>),
35    /// BETWEEN 条件
36    Between(String, SqlValue, SqlValue),
37    /// LIKE 条件
38    Like(String, String),
39    /// AND 组合
40    And(Vec<Condition>),
41    /// OR 组合
42    Or(Vec<Condition>),
43}
44
45// 实现 From trait 支持自动转换
46impl From<i32> for SqlValue {
47    fn from(v: i32) -> Self {
48        SqlValue::Int(v as i64)
49    }
50}
51
52impl From<i64> for SqlValue {
53    fn from(v: i64) -> Self {
54        SqlValue::Int(v)
55    }
56}
57
58impl From<u64> for SqlValue {
59    fn from(v: u64) -> Self {
60        SqlValue::Int(v as i64)
61    }
62}
63
64impl From<f64> for SqlValue {
65    fn from(v: f64) -> Self {
66        SqlValue::Float(v)
67    }
68}
69
70impl From<f32> for SqlValue {
71    fn from(v: f32) -> Self {
72        SqlValue::Float(v as f64)
73    }
74}
75
76impl From<String> for SqlValue {
77    fn from(v: String) -> Self {
78        SqlValue::String(v)
79    }
80}
81
82impl From<&str> for SqlValue {
83    fn from(v: &str) -> Self {
84        SqlValue::String(v.to_string())
85    }
86}
87
88impl From<bool> for SqlValue {
89    fn from(v: bool) -> Self {
90        SqlValue::Bool(v)
91    }
92}
93
94impl From<Vec<u8>> for SqlValue {
95    fn from(v: Vec<u8>) -> Self {
96        SqlValue::Bytes(v)
97    }
98}
99
100impl From<JsonValue> for SqlValue {
101    fn from(v: JsonValue) -> Self {
102        SqlValue::Json(v)
103    }
104}
105
106impl From<NaiveDateTime> for SqlValue {
107    fn from(v: NaiveDateTime) -> Self {
108        SqlValue::DateTime(v)
109    }
110}
111
112impl<T> From<Option<T>> for SqlValue
113where
114    T: Into<SqlValue>,
115{
116    fn from(v: Option<T>) -> Self {
117        match v {
118            Some(val) => val.into(),
119            None => SqlValue::Null,
120        }
121    }
122}
123
124/// 将条件转换为 SQL 字符串和参数列表
125///
126/// # 参数
127/// - condition: 要转换的条件
128/// - params: 用于收集参数的可变向量
129///
130/// # 返回
131/// - SQL 字符串片段
132pub fn condition_to_sql(condition: &Condition, params: &mut Vec<SqlValue>) -> String {
133    match condition {
134        Condition::Eq(field, value) => {
135            params.push(value.clone());
136            format!("{} = ?", field)
137        }
138        Condition::Ne(field, value) => {
139            params.push(value.clone());
140            format!("{} != ?", field)
141        }
142        Condition::Gt(field, value) => {
143            params.push(value.clone());
144            format!("{} > ?", field)
145        }
146        Condition::Lt(field, value) => {
147            params.push(value.clone());
148            format!("{} < ?", field)
149        }
150        Condition::Gte(field, value) => {
151            params.push(value.clone());
152            format!("{} >= ?", field)
153        }
154        Condition::Lte(field, value) => {
155            params.push(value.clone());
156            format!("{} <= ?", field)
157        }
158        Condition::In(field, values) => {
159            if values.is_empty() {
160                // IN 空列表总是返回 false
161                return "1 = 0".to_string();
162            }
163            let placeholders = values
164                .iter()
165                .map(|v| {
166                    params.push(v.clone());
167                    "?"
168                })
169                .collect::<Vec<_>>()
170                .join(", ");
171            format!("{} IN ({})", field, placeholders)
172        }
173        Condition::Between(field, start, end) => {
174            params.push(start.clone());
175            params.push(end.clone());
176            format!("{} BETWEEN ? AND ?", field)
177        }
178        Condition::Like(field, pattern) => {
179            params.push(SqlValue::String(pattern.clone()));
180            format!("{} LIKE ?", field)
181        }
182        Condition::And(conditions) => {
183            if conditions.is_empty() {
184                return "1 = 1".to_string();
185            }
186            if conditions.len() == 1 {
187                return condition_to_sql(&conditions[0], params);
188            }
189            // AND 条件需要括号以确保优先级
190            let parts: Vec<String> = conditions
191                .iter()
192                .map(|c| condition_to_sql(c, params))
193                .collect();
194            format!("({})", parts.join(" AND "))
195        }
196        Condition::Or(conditions) => {
197            if conditions.is_empty() {
198                return "1 = 0".to_string();
199            }
200            if conditions.len() == 1 {
201                return condition_to_sql(&conditions[0], params);
202            }
203            // OR 条件需要括号
204            let parts: Vec<String> = conditions
205                .iter()
206                .map(|c| condition_to_sql(c, params))
207                .collect();
208            format!("({})", parts.join(" OR "))
209        }
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use chrono::NaiveDate;
217
218    #[test]
219    fn test_from_i32() {
220        let value: SqlValue = 42i32.into();
221        match value {
222            SqlValue::Int(v) => assert_eq!(v, 42),
223            _ => panic!("期望 SqlValue::Int"),
224        }
225    }
226
227    #[test]
228    fn test_from_i64() {
229        let value: SqlValue = 9223372036854775807i64.into();
230        match value {
231            SqlValue::Int(v) => assert_eq!(v, 9223372036854775807),
232            _ => panic!("期望 SqlValue::Int"),
233        }
234    }
235
236    #[test]
237    fn test_from_f32() {
238        let value: SqlValue = 3.5f32.into();
239        match value {
240            SqlValue::Float(v) => assert!((v - 3.5).abs() < 0.01),
241            _ => panic!("期望 SqlValue::Float"),
242        }
243    }
244
245    #[test]
246    fn test_from_f64() {
247        let value: SqlValue = 2.5f64.into();
248        match value {
249            SqlValue::Float(v) => assert!((v - 2.5).abs() < 0.000001),
250            _ => panic!("期望 SqlValue::Float"),
251        }
252    }
253
254    #[test]
255    fn test_from_string() {
256        let value: SqlValue = String::from("测试字符串").into();
257        match value {
258            SqlValue::String(s) => assert_eq!(s, "测试字符串"),
259            _ => panic!("期望 SqlValue::String"),
260        }
261    }
262
263    #[test]
264    fn test_from_str() {
265        let value: SqlValue = "hello world".into();
266        match value {
267            SqlValue::String(s) => assert_eq!(s, "hello world"),
268            _ => panic!("期望 SqlValue::String"),
269        }
270    }
271
272    #[test]
273    fn test_from_bool_true() {
274        let value: SqlValue = true.into();
275        match value {
276            SqlValue::Bool(b) => assert!(b),
277            _ => panic!("期望 SqlValue::Bool"),
278        }
279    }
280
281    #[test]
282    fn test_from_bool_false() {
283        let value: SqlValue = false.into();
284        match value {
285            SqlValue::Bool(b) => assert!(!b),
286            _ => panic!("期望 SqlValue::Bool"),
287        }
288    }
289
290    #[test]
291    fn test_from_vec_u8() {
292        let bytes = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello"
293        let value: SqlValue = bytes.clone().into();
294        match value {
295            SqlValue::Bytes(b) => assert_eq!(b, bytes),
296            _ => panic!("期望 SqlValue::Bytes"),
297        }
298    }
299
300    #[test]
301    fn test_from_json_value() {
302        let json = serde_json::json!({
303            "name": "测试",
304            "age": 25,
305            "active": true
306        });
307        let value: SqlValue = json.clone().into();
308        match value {
309            SqlValue::Json(j) => assert_eq!(j, json),
310            _ => panic!("期望 SqlValue::Json"),
311        }
312    }
313
314    #[test]
315    fn test_from_naive_datetime() {
316        let dt = NaiveDate::from_ymd_opt(2024, 1, 15)
317            .unwrap()
318            .and_hms_opt(10, 30, 45)
319            .unwrap();
320        let value: SqlValue = dt.into();
321        match value {
322            SqlValue::DateTime(d) => assert_eq!(d, dt),
323            _ => panic!("期望 SqlValue::DateTime"),
324        }
325    }
326
327    #[test]
328    fn test_from_option_some() {
329        let value: SqlValue = Some(42i32).into();
330        match value {
331            SqlValue::Int(v) => assert_eq!(v, 42),
332            _ => panic!("期望 SqlValue::Int"),
333        }
334    }
335
336    #[test]
337    fn test_from_option_none() {
338        let value: SqlValue = None::<i32>.into();
339        match value {
340            SqlValue::Null => (),
341            _ => panic!("期望 SqlValue::Null"),
342        }
343    }
344
345    #[test]
346    fn test_from_option_string_some() {
347        let value: SqlValue = Some(String::from("测试")).into();
348        match value {
349            SqlValue::String(s) => assert_eq!(s, "测试"),
350            _ => panic!("期望 SqlValue::String"),
351        }
352    }
353
354    #[test]
355    fn test_from_option_string_none() {
356        let value: SqlValue = None::<String>.into();
357        match value {
358            SqlValue::Null => (),
359            _ => panic!("期望 SqlValue::Null"),
360        }
361    }
362
363    #[test]
364    fn test_negative_integers() {
365        let value: SqlValue = (-100i32).into();
366        match value {
367            SqlValue::Int(v) => assert_eq!(v, -100),
368            _ => panic!("期望 SqlValue::Int"),
369        }
370    }
371
372    #[test]
373    fn test_negative_floats() {
374        let value: SqlValue = (-3.5f64).into();
375        match value {
376            SqlValue::Float(v) => assert!((v + 3.5).abs() < 0.01),
377            _ => panic!("期望 SqlValue::Float"),
378        }
379    }
380
381    #[test]
382    fn test_empty_string() {
383        let value: SqlValue = "".into();
384        match value {
385            SqlValue::String(s) => assert_eq!(s, ""),
386            _ => panic!("期望 SqlValue::String"),
387        }
388    }
389
390    #[test]
391    fn test_empty_bytes() {
392        let value: SqlValue = Vec::<u8>::new().into();
393        match value {
394            SqlValue::Bytes(b) => assert!(b.is_empty()),
395            _ => panic!("期望 SqlValue::Bytes"),
396        }
397    }
398
399    #[test]
400    fn test_json_null() {
401        let json = serde_json::Value::Null;
402        let value: SqlValue = json.into();
403        match value {
404            SqlValue::Json(j) => assert!(j.is_null()),
405            _ => panic!("期望 SqlValue::Json"),
406        }
407    }
408
409    #[test]
410    fn test_json_array() {
411        let json = serde_json::json!([1, 2, 3, 4, 5]);
412        let value: SqlValue = json.clone().into();
413        match value {
414            SqlValue::Json(j) => assert_eq!(j, json),
415            _ => panic!("期望 SqlValue::Json"),
416        }
417    }
418
419    #[test]
420    fn test_unicode_string() {
421        let value: SqlValue = "你好世界 🌍".into();
422        match value {
423            SqlValue::String(s) => assert_eq!(s, "你好世界 🌍"),
424            _ => panic!("期望 SqlValue::String"),
425        }
426    }
427
428    #[test]
429    fn test_zero_values() {
430        let int_value: SqlValue = 0i32.into();
431        match int_value {
432            SqlValue::Int(v) => assert_eq!(v, 0),
433            _ => panic!("期望 SqlValue::Int"),
434        }
435
436        let float_value: SqlValue = 0.0f64.into();
437        match float_value {
438            SqlValue::Float(v) => assert_eq!(v, 0.0),
439            _ => panic!("期望 SqlValue::Float"),
440        }
441    }
442
443    // 测试 condition_to_sql 函数
444    #[test]
445    fn test_condition_eq() {
446        let mut params = Vec::new();
447        let cond = Condition::Eq("name".to_string(), SqlValue::String("test".to_string()));
448        let sql = super::condition_to_sql(&cond, &mut params);
449        assert_eq!(sql, "name = ?");
450        assert_eq!(params.len(), 1);
451    }
452
453    #[test]
454    fn test_condition_ne() {
455        let mut params = Vec::new();
456        let cond = Condition::Ne("status".to_string(), SqlValue::Int(1));
457        let sql = super::condition_to_sql(&cond, &mut params);
458        assert_eq!(sql, "status != ?");
459        assert_eq!(params.len(), 1);
460    }
461
462    #[test]
463    fn test_condition_gt() {
464        let mut params = Vec::new();
465        let cond = Condition::Gt("age".to_string(), SqlValue::Int(18));
466        let sql = super::condition_to_sql(&cond, &mut params);
467        assert_eq!(sql, "age > ?");
468        assert_eq!(params.len(), 1);
469    }
470
471    #[test]
472    fn test_condition_lt() {
473        let mut params = Vec::new();
474        let cond = Condition::Lt("price".to_string(), SqlValue::Float(100.0));
475        let sql = super::condition_to_sql(&cond, &mut params);
476        assert_eq!(sql, "price < ?");
477        assert_eq!(params.len(), 1);
478    }
479
480    #[test]
481    fn test_condition_gte() {
482        let mut params = Vec::new();
483        let cond = Condition::Gte("score".to_string(), SqlValue::Int(60));
484        let sql = super::condition_to_sql(&cond, &mut params);
485        assert_eq!(sql, "score >= ?");
486        assert_eq!(params.len(), 1);
487    }
488
489    #[test]
490    fn test_condition_lte() {
491        let mut params = Vec::new();
492        let cond = Condition::Lte("count".to_string(), SqlValue::Int(10));
493        let sql = super::condition_to_sql(&cond, &mut params);
494        assert_eq!(sql, "count <= ?");
495        assert_eq!(params.len(), 1);
496    }
497
498    #[test]
499    fn test_condition_in() {
500        let mut params = Vec::new();
501        let cond = Condition::In(
502            "id".to_string(),
503            vec![SqlValue::Int(1), SqlValue::Int(2), SqlValue::Int(3)],
504        );
505        let sql = super::condition_to_sql(&cond, &mut params);
506        assert_eq!(sql, "id IN (?, ?, ?)");
507        assert_eq!(params.len(), 3);
508    }
509
510    #[test]
511    fn test_condition_in_empty() {
512        let mut params = Vec::new();
513        let cond = Condition::In("id".to_string(), vec![]);
514        let sql = super::condition_to_sql(&cond, &mut params);
515        assert_eq!(sql, "1 = 0");
516        assert_eq!(params.len(), 0);
517    }
518
519    #[test]
520    fn test_condition_between() {
521        let mut params = Vec::new();
522        let cond = Condition::Between("age".to_string(), SqlValue::Int(18), SqlValue::Int(65));
523        let sql = super::condition_to_sql(&cond, &mut params);
524        assert_eq!(sql, "age BETWEEN ? AND ?");
525        assert_eq!(params.len(), 2);
526    }
527
528    #[test]
529    fn test_condition_like() {
530        let mut params = Vec::new();
531        let cond = Condition::Like("name".to_string(), "%test%".to_string());
532        let sql = super::condition_to_sql(&cond, &mut params);
533        assert_eq!(sql, "name LIKE ?");
534        assert_eq!(params.len(), 1);
535    }
536
537    #[test]
538    fn test_condition_and() {
539        let mut params = Vec::new();
540        let cond = Condition::And(vec![
541            Condition::Eq("name".to_string(), SqlValue::String("test".to_string())),
542            Condition::Gt("age".to_string(), SqlValue::Int(18)),
543        ]);
544        let sql = super::condition_to_sql(&cond, &mut params);
545        assert_eq!(sql, "(name = ? AND age > ?)");
546        assert_eq!(params.len(), 2);
547    }
548
549    #[test]
550    fn test_condition_or() {
551        let mut params = Vec::new();
552        let cond = Condition::Or(vec![
553            Condition::Eq("status".to_string(), SqlValue::Int(1)),
554            Condition::Eq("status".to_string(), SqlValue::Int(2)),
555        ]);
556        let sql = super::condition_to_sql(&cond, &mut params);
557        assert_eq!(sql, "(status = ? OR status = ?)");
558        assert_eq!(params.len(), 2);
559    }
560
561    #[test]
562    fn test_condition_and_or_priority() {
563        let mut params = Vec::new();
564        // (name = 'test' OR name = 'demo') AND age > 18
565        let cond = Condition::And(vec![
566            Condition::Or(vec![
567                Condition::Eq("name".to_string(), SqlValue::String("test".to_string())),
568                Condition::Eq("name".to_string(), SqlValue::String("demo".to_string())),
569            ]),
570            Condition::Gt("age".to_string(), SqlValue::Int(18)),
571        ]);
572        let sql = super::condition_to_sql(&cond, &mut params);
573        // OR 条件应该被括号包围
574        assert_eq!(sql, "((name = ? OR name = ?) AND age > ?)");
575        assert_eq!(params.len(), 3);
576    }
577
578    #[test]
579    fn test_condition_empty_and() {
580        let mut params = Vec::new();
581        let cond = Condition::And(vec![]);
582        let sql = super::condition_to_sql(&cond, &mut params);
583        assert_eq!(sql, "1 = 1");
584        assert_eq!(params.len(), 0);
585    }
586
587    #[test]
588    fn test_condition_empty_or() {
589        let mut params = Vec::new();
590        let cond = Condition::Or(vec![]);
591        let sql = super::condition_to_sql(&cond, &mut params);
592        assert_eq!(sql, "1 = 0");
593        assert_eq!(params.len(), 0);
594    }
595
596    #[test]
597    fn test_condition_single_and() {
598        let mut params = Vec::new();
599        let cond = Condition::And(vec![Condition::Eq("id".to_string(), SqlValue::Int(1))]);
600        let sql = super::condition_to_sql(&cond, &mut params);
601        assert_eq!(sql, "id = ?");
602        assert_eq!(params.len(), 1);
603    }
604
605    #[test]
606    fn test_condition_single_or() {
607        let mut params = Vec::new();
608        let cond = Condition::Or(vec![Condition::Eq("id".to_string(), SqlValue::Int(1))]);
609        let sql = super::condition_to_sql(&cond, &mut params);
610        assert_eq!(sql, "id = ?");
611        assert_eq!(params.len(), 1);
612    }
613}
614
615#[cfg(test)]
616mod property_tests {
617    use super::*;
618    use proptest::prelude::*;
619
620    // 生成有效的字段名(字母开头,后跟字母数字下划线)
621    fn field_name_strategy() -> impl Strategy<Value = String> {
622        "[a-z][a-z0-9_]{0,30}"
623    }
624
625    // 生成 SqlValue 策略
626    fn sql_value_strategy() -> impl Strategy<Value = SqlValue> {
627        prop_oneof![
628            Just(SqlValue::Null),
629            any::<bool>().prop_map(SqlValue::Bool),
630            any::<i64>().prop_map(SqlValue::Int),
631            any::<f64>().prop_map(|f| {
632                if f.is_finite() {
633                    SqlValue::Float(f)
634                } else {
635                    SqlValue::Float(0.0)
636                }
637            }),
638            "[a-zA-Z0-9_\\s]{0,50}".prop_map(SqlValue::String),
639        ]
640    }
641
642    // **Feature: mysql-query-builder, Property 5: 操作符支持**
643    // **验证需求:3.3**
644    //
645    // 属性:对于任意支持的操作符(=, !=, >, <, >=, <=, in, between, like),
646    // 生成的 SQL 应该包含正确的操作符语法
647    //
648    // 此测试验证所有支持的操作符都能正确生成 SQL 语句,
649    // 确保参数化查询的正确性和 SQL 注入防护。
650    proptest! {
651        #![proptest_config(ProptestConfig::with_cases(100))]
652
653        #[test]
654        fn prop_operator_eq_support(
655            field in field_name_strategy(),
656            value in sql_value_strategy()
657        ) {
658            let mut params = Vec::new();
659            let cond = Condition::Eq(field.clone(), value);
660            let sql = condition_to_sql(&cond, &mut params);
661
662            // 验证 SQL 包含正确的操作符
663            let expected = format!("{} = {{placeholder}}", field).replace("{placeholder}", "?");
664            prop_assert!(sql.contains(&expected));
665            prop_assert_eq!(params.len(), 1);
666        }
667
668        #[test]
669        fn prop_operator_ne_support(
670            field in field_name_strategy(),
671            value in sql_value_strategy()
672        ) {
673            let mut params = Vec::new();
674            let cond = Condition::Ne(field.clone(), value);
675            let sql = condition_to_sql(&cond, &mut params);
676
677            let expected = format!("{} != {{placeholder}}", field).replace("{placeholder}", "?");
678            prop_assert!(sql.contains(&expected));
679            prop_assert_eq!(params.len(), 1);
680        }
681
682        #[test]
683        fn prop_operator_gt_support(
684            field in field_name_strategy(),
685            value in sql_value_strategy()
686        ) {
687            let mut params = Vec::new();
688            let cond = Condition::Gt(field.clone(), value);
689            let sql = condition_to_sql(&cond, &mut params);
690
691            let expected = format!("{} > {{placeholder}}", field).replace("{placeholder}", "?");
692            prop_assert!(sql.contains(&expected));
693            prop_assert_eq!(params.len(), 1);
694        }
695
696        #[test]
697        fn prop_operator_lt_support(
698            field in field_name_strategy(),
699            value in sql_value_strategy()
700        ) {
701            let mut params = Vec::new();
702            let cond = Condition::Lt(field.clone(), value);
703            let sql = condition_to_sql(&cond, &mut params);
704
705            let expected = format!("{} < {{placeholder}}", field).replace("{placeholder}", "?");
706            prop_assert!(sql.contains(&expected));
707            prop_assert_eq!(params.len(), 1);
708        }
709
710        #[test]
711        fn prop_operator_gte_support(
712            field in field_name_strategy(),
713            value in sql_value_strategy()
714        ) {
715            let mut params = Vec::new();
716            let cond = Condition::Gte(field.clone(), value);
717            let sql = condition_to_sql(&cond, &mut params);
718
719            let expected = format!("{} >= {{placeholder}}", field).replace("{placeholder}", "?");
720            prop_assert!(sql.contains(&expected));
721            prop_assert_eq!(params.len(), 1);
722        }
723
724        #[test]
725        fn prop_operator_lte_support(
726            field in field_name_strategy(),
727            value in sql_value_strategy()
728        ) {
729            let mut params = Vec::new();
730            let cond = Condition::Lte(field.clone(), value);
731            let sql = condition_to_sql(&cond, &mut params);
732
733            let expected = format!("{} <= {{placeholder}}", field).replace("{placeholder}", "?");
734            prop_assert!(sql.contains(&expected));
735            prop_assert_eq!(params.len(), 1);
736        }
737
738        #[test]
739        fn prop_operator_in_support(
740            field in field_name_strategy(),
741            values in prop::collection::vec(sql_value_strategy(), 1..10)
742        ) {
743            let mut params = Vec::new();
744            let values_len = values.len();
745            let cond = Condition::In(field.clone(), values);
746            let sql = condition_to_sql(&cond, &mut params);
747
748            let expected = format!("{} IN", field);
749            prop_assert!(sql.contains(&expected));
750            prop_assert_eq!(params.len(), values_len);
751        }
752
753        #[test]
754        fn prop_operator_between_support(
755            field in field_name_strategy(),
756            start in sql_value_strategy(),
757            end in sql_value_strategy()
758        ) {
759            let mut params = Vec::new();
760            let cond = Condition::Between(field.clone(), start, end);
761            let sql = condition_to_sql(&cond, &mut params);
762
763            let expected = format!("{} BETWEEN {{p1}} AND {{p2}}", field)
764                .replace("{p1}", "?")
765                .replace("{p2}", "?");
766            prop_assert!(sql.contains(&expected));
767            prop_assert_eq!(params.len(), 2);
768        }
769
770        #[test]
771        fn prop_operator_like_support(
772            field in field_name_strategy(),
773            pattern in "[a-zA-Z0-9_%]{1,20}"
774        ) {
775            let mut params = Vec::new();
776            let cond = Condition::Like(field.clone(), pattern);
777            let sql = condition_to_sql(&cond, &mut params);
778
779            let expected = format!("{} LIKE {{placeholder}}", field).replace("{placeholder}", "?");
780            prop_assert!(sql.contains(&expected));
781            prop_assert_eq!(params.len(), 1);
782        }
783    }
784
785    // Feature: mysql-query-builder, Property 9: AND/OR 优先级处理
786    // 验证需求:3.7
787    proptest! {
788        #![proptest_config(ProptestConfig::with_cases(100))]
789
790        #[test]
791        fn prop_and_or_priority_handling(
792            field1 in field_name_strategy(),
793            field2 in field_name_strategy(),
794            value1 in sql_value_strategy(),
795            value2 in sql_value_strategy(),
796            value3 in sql_value_strategy()
797        ) {
798            let mut params = Vec::new();
799
800            // 构建 (field1 = value1 OR field1 = value2) AND field2 = value3
801            let cond = Condition::And(vec![
802                Condition::Or(vec![
803                    Condition::Eq(field1.clone(), value1),
804                    Condition::Eq(field1.clone(), value2),
805                ]),
806                Condition::Eq(field2.clone(), value3),
807            ]);
808
809            let sql = condition_to_sql(&cond, &mut params);
810
811            // 验证 SQL 有正确的括号确保操作符优先级
812            // 整个条件应该被括号包围
813            prop_assert!(sql.starts_with('('));
814            prop_assert!(sql.ends_with(')'));
815
816            // OR 条件应该被括号包围
817            prop_assert!(sql.contains(" OR "));
818            prop_assert!(sql.contains(" AND "));
819
820            // 参数数量应该正确
821            prop_assert_eq!(params.len(), 3);
822        }
823
824        #[test]
825        fn prop_nested_and_or_brackets(
826            field in field_name_strategy(),
827            values in prop::collection::vec(sql_value_strategy(), 2..5)
828        ) {
829            let mut params = Vec::new();
830
831            // 构建多个 OR 条件的 AND 组合
832            let or_conditions: Vec<Condition> = values
833                .iter()
834                .map(|v| Condition::Eq(field.clone(), v.clone()))
835                .collect();
836
837            let cond = Condition::And(vec![
838                Condition::Or(or_conditions.clone()),
839                Condition::Gt(field.clone(), SqlValue::Int(0)),
840            ]);
841
842            let sql = condition_to_sql(&cond, &mut params);
843
844            // 验证括号匹配
845            let open_count = sql.chars().filter(|&c| c == '(').count();
846            let close_count = sql.chars().filter(|&c| c == ')').count();
847            prop_assert_eq!(open_count, close_count);
848
849            // 验证参数数量
850            prop_assert_eq!(params.len(), values.len() + 1);
851        }
852
853        #[test]
854        fn prop_multiple_and_conditions(
855            field in field_name_strategy(),
856            values in prop::collection::vec(sql_value_strategy(), 2..5)
857        ) {
858            let mut params = Vec::new();
859
860            // 构建多个 AND 条件
861            let and_conditions: Vec<Condition> = values
862                .iter()
863                .enumerate()
864                .map(|(i, v)| {
865                    if i % 2 == 0 {
866                        Condition::Eq(field.clone(), v.clone())
867                    } else {
868                        Condition::Ne(field.clone(), v.clone())
869                    }
870                })
871                .collect();
872
873            let cond = Condition::And(and_conditions);
874            let sql = condition_to_sql(&cond, &mut params);
875
876            // 验证 AND 连接
877            let and_count = sql.matches(" AND ").count();
878            prop_assert_eq!(and_count, values.len() - 1);
879
880            // 验证参数数量
881            prop_assert_eq!(params.len(), values.len());
882        }
883
884        #[test]
885        fn prop_or_conditions_always_bracketed(
886            field in field_name_strategy(),
887            values in prop::collection::vec(sql_value_strategy(), 2..5)
888        ) {
889            let mut params = Vec::new();
890
891            // 构建 OR 条件
892            let or_conditions: Vec<Condition> = values
893                .iter()
894                .map(|v| Condition::Eq(field.clone(), v.clone()))
895                .collect();
896
897            let cond = Condition::Or(or_conditions);
898            let sql = condition_to_sql(&cond, &mut params);
899
900            // OR 条件应该被括号包围
901            prop_assert!(sql.starts_with('('));
902            prop_assert!(sql.ends_with(')'));
903
904            // 验证 OR 连接
905            let or_count = sql.matches(" OR ").count();
906            prop_assert_eq!(or_count, values.len() - 1);
907        }
908    }
909}