halo/
structs.rs

1//! Struct:轻量 ORM(参考 go-sqlbuilder 的 Struct/structfields 实现)。
2//!
3//! Rust 无运行时反射;在“不新增 proc-macro crate”的约束下,本实现通过 `macro_rules!`
4//! 为 struct 生成字段元数据与取值逻辑,从而提供与 go-sqlbuilder 接近的体验。
5
6use crate::delete::DeleteBuilder;
7use crate::escape_all;
8use crate::field_mapper::{FieldMapperFunc, default_field_mapper};
9use crate::flavor::Flavor;
10use crate::insert::InsertBuilder;
11use crate::select::SelectBuilder;
12use crate::select_cols;
13use crate::update::UpdateBuilder;
14use std::any::Any;
15use std::collections::HashSet;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum FieldOpt {
19    WithQuote,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct FieldMeta {
24    /// Rust 字段名(用于生成取值代码)
25    pub rust: &'static str,
26    /// 用于 FieldMapper 的“原始字段名”(对齐 go 的 reflect.StructField.Name)。
27    ///
28    /// Rust 无法获得运行时字段名;这里由宏生成,默认等于 `rust`,
29    /// 但测试/用户可以显式指定以对齐 go 的 CamelCase 命名。
30    pub orig: &'static str,
31    /// SQL 列名(db tag / mapper 之后)
32    pub db: &'static str,
33    /// 可选别名(AS)
34    pub as_: Option<&'static str>,
35    /// tags
36    pub tags: &'static [&'static str],
37    /// omitempty tags(包含 "" 表示默认)
38    pub omitempty_tags: &'static [&'static str],
39    pub with_quote: bool,
40}
41
42impl FieldMeta {
43    pub fn name_for_select(&self, flavor: Flavor, alias: &str) -> String {
44        let base = if self.with_quote {
45            flavor.quote(alias)
46        } else {
47            alias.to_string()
48        };
49        if let Some(as_) = self.as_ {
50            format!("{base} AS {as_}")
51        } else {
52            base
53        }
54    }
55}
56
57fn is_ignored(fm: &FieldMeta) -> bool {
58    // 对齐 go 的 `db:"-"`:忽略该字段
59    fm.db == "-"
60}
61
62/// 由宏为你的业务 struct 实现的 trait:提供字段元数据与取值/空值判断。
63pub trait SqlStruct: Sized {
64    const FIELDS: &'static [FieldMeta];
65
66    /// 取字段的值用于 INSERT/UPDATE(按 FIELDS 顺序)。
67    fn values(&self) -> Vec<crate::modifiers::Arg>;
68
69    /// 判断某个字段是否“空值”(用于 omitempty)。
70    fn is_empty_field(&self, rust_field: &'static str) -> bool;
71
72    /// 返回可写入的扫描目标列表(用于 `Struct::addr*`)。
73    ///
74    /// 说明:为了避免 Rust 借用检查器对“多次可变借用同一 struct”的限制,
75    /// 这里一次性生成全部 `ScanCell`(内部用 raw pointer 持有字段地址)。
76    fn addr_cells<'a>(
77        &'a mut self,
78        rust_fields: &[&'static str],
79    ) -> Option<Vec<crate::scan::ScanCell<'a>>>;
80}
81
82/// 判断“空值”的 trait(用于实现 go-sqlbuilder 的 omitempty 语义子集)。
83pub trait IsEmpty {
84    fn is_empty_value(&self) -> bool;
85}
86
87impl IsEmpty for String {
88    fn is_empty_value(&self) -> bool {
89        self.is_empty()
90    }
91}
92
93impl IsEmpty for &str {
94    fn is_empty_value(&self) -> bool {
95        self.is_empty()
96    }
97}
98
99impl IsEmpty for bool {
100    fn is_empty_value(&self) -> bool {
101        !*self
102    }
103}
104
105macro_rules! empty_num {
106    ($($t:ty),+ $(,)?) => {
107        $(impl IsEmpty for $t {
108            fn is_empty_value(&self) -> bool {
109                *self == 0 as $t
110            }
111        })+
112    };
113}
114
115empty_num!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
116
117impl IsEmpty for f64 {
118    fn is_empty_value(&self) -> bool {
119        // 对齐 go:用 bits 判断 0(避免 -0.0 的边界差异)
120        self.to_bits() == 0
121    }
122}
123
124impl<T: IsEmpty> IsEmpty for Option<T> {
125    fn is_empty_value(&self) -> bool {
126        match self {
127            None => true,
128            Some(v) => v.is_empty_value(),
129        }
130    }
131}
132
133impl<T> IsEmpty for Vec<T> {
134    fn is_empty_value(&self) -> bool {
135        self.is_empty()
136    }
137}
138
139impl IsEmpty for Box<dyn crate::valuer::SqlValuer> {
140    fn is_empty_value(&self) -> bool {
141        // 对齐 go 的指针语义:非 nil 指针不是 empty。
142        false
143    }
144}
145
146pub struct Struct<T: SqlStruct> {
147    pub flavor: Flavor,
148    mapper: FieldMapperFunc,
149    with_tags: Vec<&'static str>,
150    without_tags: Vec<&'static str>,
151    _phantom: std::marker::PhantomData<T>,
152}
153
154impl<T: SqlStruct> Clone for Struct<T> {
155    fn clone(&self) -> Self {
156        Self {
157            flavor: self.flavor,
158            mapper: self.mapper.clone(),
159            with_tags: self.with_tags.clone(),
160            without_tags: self.without_tags.clone(),
161            _phantom: std::marker::PhantomData,
162        }
163    }
164}
165
166impl<T: SqlStruct> std::fmt::Debug for Struct<T> {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        // mapper 无法 Debug;这里只输出关键信息,避免影响使用与测试。
169        f.debug_struct("Struct")
170            .field("flavor", &self.flavor)
171            .field("with_tags", &self.with_tags)
172            .field("without_tags", &self.without_tags)
173            .finish()
174    }
175}
176
177impl<T: SqlStruct> Default for Struct<T> {
178    fn default() -> Self {
179        Self {
180            flavor: crate::default_flavor(),
181            mapper: default_field_mapper(),
182            with_tags: Vec::new(),
183            without_tags: Vec::new(),
184            _phantom: std::marker::PhantomData,
185        }
186    }
187}
188
189impl<T: SqlStruct> Struct<T> {
190    pub fn new() -> Self {
191        Self::default()
192    }
193
194    /// WithFieldMapper:返回 shadow copy,并覆盖当前 Struct 的 mapper(对齐 go `Struct.WithFieldMapper`)。
195    ///
196    /// - 传入 `identity_mapper()` 等价于 go 的 `WithFieldMapper(nil)`。
197    pub fn with_field_mapper(&self, mapper: FieldMapperFunc) -> Self {
198        let mut c = self.clone();
199        c.mapper = mapper;
200        c
201    }
202
203    fn has_defined_tag(tag: &str) -> bool {
204        if tag.is_empty() {
205            return false;
206        }
207        T::FIELDS
208            .iter()
209            .any(|f| !is_ignored(f) && f.tags.contains(&tag))
210    }
211
212    /// ForFlavor:返回 shadow copy(对齐 go `Struct.For`),不修改原对象。
213    pub fn for_flavor(&self, flavor: Flavor) -> Self {
214        let mut c = self.clone();
215        c.flavor = flavor;
216        c
217    }
218
219    /// WithTag:返回 shadow copy(对齐 go `Struct.WithTag`),不修改原对象。
220    pub fn with_tag(&self, tags: impl IntoIterator<Item = &'static str>) -> Self {
221        let mut c = self.clone();
222        for t in tags {
223            if t.is_empty() {
224                continue;
225            }
226            if !c.with_tags.contains(&t) {
227                c.with_tags.push(t);
228            }
229        }
230        c.with_tags.sort_unstable();
231        c.with_tags.dedup();
232        c
233    }
234
235    /// WithoutTag:返回 shadow copy(对齐 go `Struct.WithoutTag`),不修改原对象。
236    pub fn without_tag(&self, tags: impl IntoIterator<Item = &'static str>) -> Self {
237        let mut c = self.clone();
238        for t in tags {
239            if t.is_empty() {
240                continue;
241            }
242            if !c.without_tags.contains(&t) {
243                c.without_tags.push(t);
244            }
245        }
246        c.without_tags.sort_unstable();
247        c.without_tags.dedup();
248        // 过滤 with_tags
249        c.with_tags.retain(|t| !c.without_tags.contains(t));
250        c
251    }
252
253    fn should_omit_empty(&self, fm: &FieldMeta) -> bool {
254        // 对齐 go 的 structField.ShouldOmitEmpty(with...):
255        // - 先看默认 tag ""
256        // - 再看 with tags
257        let omit = fm.omitempty_tags;
258        if omit.is_empty() {
259            return false;
260        }
261        if omit.contains(&"") {
262            return true;
263        }
264        self.with_tags.iter().any(|t| omit.contains(t))
265    }
266
267    fn excluded_by_without(&self, fm: &FieldMeta) -> bool {
268        self.without_tags.iter().any(|t| fm.tags.contains(t))
269    }
270
271    fn alias_of(&self, fm: &FieldMeta) -> String {
272        if is_ignored(fm) {
273            return String::new();
274        }
275
276        if !fm.db.is_empty() {
277            return fm.db.to_string();
278        }
279
280        let mapped = (self.mapper)(fm.orig);
281        if mapped.is_empty() {
282            fm.orig.to_string()
283        } else {
284            mapped
285        }
286    }
287
288    fn read_key_of(&self, fm: &FieldMeta) -> String {
289        // 对齐 go structField.Key:优先 As,否则 Alias,否则 Name
290        if let Some(as_) = fm.as_ {
291            return as_.to_string();
292        }
293        let a = self.alias_of(fm);
294        if a.is_empty() { fm.rust.to_string() } else { a }
295    }
296
297    fn write_key_of(&self, fm: &FieldMeta) -> String {
298        // 对齐 go ForWrite:按 Alias 去重
299        let a = self.alias_of(fm);
300        if a.is_empty() { fm.rust.to_string() } else { a }
301    }
302
303    fn fields_for_read(&self) -> Vec<&'static FieldMeta> {
304        self.fields_filtered(true)
305    }
306
307    fn fields_for_write(&self) -> Vec<&'static FieldMeta> {
308        self.fields_filtered(false)
309    }
310
311    fn fields_filtered(&self, for_read: bool) -> Vec<&'static FieldMeta> {
312        let mut out = Vec::new();
313        let mut seen = HashSet::<String>::new();
314
315        let push_field = |out: &mut Vec<&'static FieldMeta>,
316                          seen: &mut HashSet<String>,
317                          fm: &'static FieldMeta,
318                          for_read: bool| {
319            if is_ignored(fm) {
320                return;
321            }
322            if self.excluded_by_without(fm) {
323                return;
324            }
325            let key = if for_read {
326                self.read_key_of(fm)
327            } else {
328                self.write_key_of(fm)
329            };
330            if seen.insert(key) {
331                out.push(fm);
332            }
333        };
334
335        if self.with_tags.is_empty() {
336            for fm in T::FIELDS {
337                push_field(&mut out, &mut seen, fm, for_read);
338            }
339            return out;
340        }
341
342        // 对齐 go FilterTags(with...): 按 with_tags 顺序(这里已排序)逐个 tag 抽取字段并去重
343        for tag in &self.with_tags {
344            for fm in T::FIELDS {
345                if fm.tags.contains(tag) {
346                    push_field(&mut out, &mut seen, fm, for_read);
347                }
348            }
349        }
350
351        out
352    }
353
354    fn parse_table_alias(table: &str) -> &str {
355        // 与 go 实现一致:取最后一个空格后的 token
356        table.rsplit_once(' ').map(|(_, a)| a).unwrap_or(table)
357    }
358
359    /// Columns:对齐 go-sqlbuilder `Struct.Columns()`(返回 ForWrite 的未 quote 列名)。
360    pub fn columns(&self) -> Vec<String> {
361        self.fields_for_write()
362            .into_iter()
363            .map(|f| self.alias_of(f))
364            .collect()
365    }
366
367    /// ColumnsForTag:对齐 go-sqlbuilder `Struct.ColumnsForTag(tag)`。
368    ///
369    /// - 如果 tag 不存在,返回 None(对齐 go 返回 nil)
370    pub fn columns_for_tag(&self, tag: &str) -> Option<Vec<String>> {
371        if !Self::has_defined_tag(tag) {
372            return None;
373        }
374        // API 约束:当前实现需要 &'static str;这里为对齐 go 的便捷接口,做一次泄漏。
375        // 后续如果要严格控制内存,可把 tags 改为 Cow<'static, str>。
376        let tag: &'static str = Box::leak(tag.to_string().into_boxed_str());
377        Some(self.with_tag([tag]).columns())
378    }
379
380    /// Values:对齐 go-sqlbuilder `Struct.Values()`(返回 ForWrite 的值,顺序与 `columns()` 一致)。
381    pub fn values(&self, v: &T) -> Vec<crate::modifiers::Arg> {
382        let all = v.values();
383        let mut out = Vec::new();
384        for (fm, arg) in T::FIELDS.iter().zip(all) {
385            if is_ignored(fm) || self.excluded_by_without(fm) {
386                continue;
387            }
388            if self.with_tags.is_empty() || self.with_tags.iter().any(|t| fm.tags.contains(t)) {
389                out.push(arg);
390            }
391        }
392        // 注意:上面是“声明顺序”而不是 “tag 分组顺序”;
393        // 为与 go 完全一致(多 tag 时按 tag 分组 + 去重),这里用 fields_for_write 再重排。
394        let mut map = std::collections::HashMap::<&'static str, crate::modifiers::Arg>::new();
395        for (fm, arg) in T::FIELDS.iter().zip(v.values()) {
396            map.insert(fm.rust, arg);
397        }
398        self.fields_for_write()
399            .into_iter()
400            .filter_map(|fm| map.get(fm.rust).cloned())
401            .collect()
402    }
403
404    /// ValuesForTag:对齐 go-sqlbuilder `Struct.ValuesForTag(tag, value)`。
405    ///
406    /// - 如果 tag 不存在,返回 None(对齐 go 返回 nil)
407    pub fn values_for_tag(&self, tag: &str, v: &T) -> Option<Vec<crate::modifiers::Arg>> {
408        if !Self::has_defined_tag(tag) {
409            return None;
410        }
411        let tag: &'static str = Box::leak(tag.to_string().into_boxed_str());
412        Some(self.with_tag([tag]).values(v))
413    }
414
415    /// ForeachRead:对齐 go-sqlbuilder `Struct.ForeachRead`。
416    ///
417    /// - `dbtag`:等价 go 的 `sf.DBTag`(可能为空字符串)
418    /// - `is_quoted`:等价 go 的 `sf.IsQuoted`
419    /// - `field_meta`:Rust 侧提供的字段元信息(替代 go 的 `reflect.StructField`)
420    pub fn foreach_read(&self, mut trans: impl FnMut(&str, bool, &FieldMeta)) {
421        for fm in self.fields_for_read() {
422            trans(fm.db, fm.with_quote, fm);
423        }
424    }
425
426    /// ForeachWrite:对齐 go-sqlbuilder `Struct.ForeachWrite`。
427    pub fn foreach_write(&self, mut trans: impl FnMut(&str, bool, &FieldMeta)) {
428        for fm in self.fields_for_write() {
429            trans(fm.db, fm.with_quote, fm);
430        }
431    }
432
433    /// Addr:对齐 go-sqlbuilder `Struct.Addr(st)`(返回 ForRead 的“写入目标”列表)。
434    pub fn addr<'a>(&self, st: &'a mut T) -> Vec<crate::scan::ScanCell<'a>> {
435        let rust_fields: Vec<&'static str> = self
436            .fields_for_read()
437            .into_iter()
438            .map(|fm| fm.rust)
439            .collect();
440        st.addr_cells(&rust_fields).unwrap_or_default()
441    }
442
443    /// AddrForTag:对齐 go-sqlbuilder `Struct.AddrForTag(tag, st)`。
444    /// tag 不存在返回 None(对齐 go 返回 nil)。
445    pub fn addr_for_tag<'a>(
446        &self,
447        tag: &str,
448        st: &'a mut T,
449    ) -> Option<Vec<crate::scan::ScanCell<'a>>> {
450        if !Self::has_defined_tag(tag) {
451            return None;
452        }
453        let tag: &'static str = Box::leak(tag.to_string().into_boxed_str());
454        Some(self.with_tag([tag]).addr(st))
455    }
456
457    /// AddrWithCols:对齐 go-sqlbuilder `Struct.AddrWithCols(cols, st)`。
458    /// 如果 cols 中任一列找不到,返回 None(对齐 go 返回 nil)。
459    pub fn addr_with_cols<'a>(
460        &self,
461        cols: &[&str],
462        st: &'a mut T,
463    ) -> Option<Vec<crate::scan::ScanCell<'a>>> {
464        let fields = self.fields_for_read();
465        let mut map = std::collections::HashMap::<String, &'static str>::new();
466        for fm in fields {
467            let key = self.read_key_of(fm);
468            map.insert(key, fm.rust);
469        }
470
471        let mut rust_fields = Vec::with_capacity(cols.len());
472        for &c in cols {
473            rust_fields.push(*map.get(c)?);
474        }
475        st.addr_cells(&rust_fields)
476    }
477
478    pub fn select_from(&self, table: &str) -> SelectBuilder {
479        let mut sb = SelectBuilder::new();
480        sb.set_flavor(self.flavor);
481        sb.from([table.to_string()]);
482
483        let alias = Self::parse_table_alias(table);
484        let cols: Vec<String> = self
485            .fields_for_read()
486            .into_iter()
487            .map(|f| {
488                let field_alias = self.alias_of(f);
489                let mut c = String::new();
490                // 对齐 go:只检查 sf.Alias(db)是否包含 '.'
491                if self.flavor != Flavor::CQL && !field_alias.contains('.') {
492                    c.push_str(alias);
493                    c.push('.');
494                }
495                c.push_str(&f.name_for_select(self.flavor, &field_alias));
496                c
497            })
498            .collect();
499
500        if cols.is_empty() {
501            select_cols!(sb, "*");
502        } else {
503            sb.select(cols);
504        }
505        sb
506    }
507
508    /// SelectFromForTag:对齐 go-sqlbuilder `SelectFromForTag(table, tag)`(deprecated)。
509    pub fn select_from_for_tag(&self, table: &str, tag: &str) -> SelectBuilder {
510        // go:如果 tag 不存在,则 SELECT *;这里复用现有行为:with_tag 后 cols 为空 => select "*"
511        let tag: &'static str = Box::leak(tag.to_string().into_boxed_str());
512        self.with_tag([tag]).select_from(table)
513    }
514
515    pub fn update(&self, table: &str, value: &T) -> UpdateBuilder {
516        let mut ub = UpdateBuilder::new();
517        ub.set_flavor(self.flavor);
518        ub.update([table.to_string()]);
519
520        let mut assigns = Vec::new();
521
522        let mut map = std::collections::HashMap::<&'static str, crate::modifiers::Arg>::new();
523        for (fm, arg) in T::FIELDS.iter().zip(value.values()) {
524            map.insert(fm.rust, arg);
525        }
526
527        for fm in self.fields_for_write() {
528            if self.should_omit_empty(fm) && value.is_empty_field(fm.rust) {
529                continue;
530            }
531            // 对齐 go 的 withquote:写入时也需要 quote 列名。
532            let field_alias = self.alias_of(fm);
533            let col = if fm.with_quote {
534                self.flavor.quote(&field_alias)
535            } else {
536                field_alias
537            };
538            if let Some(v) = map.get(fm.rust).cloned() {
539                assigns.push(ub.assign(&col, v));
540            }
541        }
542
543        ub.set(assigns);
544        ub
545    }
546
547    /// UpdateForTag:对齐 go-sqlbuilder `UpdateForTag(table, tag, value)`(deprecated)。
548    pub fn update_for_tag(&self, table: &str, tag: &str, value: &T) -> UpdateBuilder {
549        let tag: &'static str = Box::leak(tag.to_string().into_boxed_str());
550        self.with_tag([tag]).update(table, value)
551    }
552
553    pub fn delete_from(&self, table: &str) -> DeleteBuilder {
554        let mut db = DeleteBuilder::new();
555        db.set_flavor(self.flavor);
556        db.delete_from([table.to_string()]);
557        db
558    }
559
560    pub fn insert_into<'a>(
561        &self,
562        table: &str,
563        rows: impl IntoIterator<Item = &'a T>,
564    ) -> InsertBuilder
565    where
566        T: 'a,
567    {
568        self.insert_internal(table, rows, InsertVerb::Insert)
569    }
570
571    /// InsertIntoForTag:对齐 go-sqlbuilder `InsertIntoForTag(table, tag, value...)`(deprecated)。
572    pub fn insert_into_for_tag<'a>(
573        &self,
574        table: &str,
575        tag: &str,
576        rows: impl IntoIterator<Item = &'a T>,
577    ) -> InsertBuilder
578    where
579        T: 'a,
580    {
581        let tag: &'static str = Box::leak(tag.to_string().into_boxed_str());
582        self.with_tag([tag]).insert_into(table, rows)
583    }
584
585    pub fn insert_ignore_into_for_tag<'a>(
586        &self,
587        table: &str,
588        tag: &str,
589        rows: impl IntoIterator<Item = &'a T>,
590    ) -> InsertBuilder
591    where
592        T: 'a,
593    {
594        let tag: &'static str = Box::leak(tag.to_string().into_boxed_str());
595        self.with_tag([tag]).insert_ignore_into(table, rows)
596    }
597
598    pub fn replace_into_for_tag<'a>(
599        &self,
600        table: &str,
601        tag: &str,
602        rows: impl IntoIterator<Item = &'a T>,
603    ) -> InsertBuilder
604    where
605        T: 'a,
606    {
607        let tag: &'static str = Box::leak(tag.to_string().into_boxed_str());
608        self.with_tag([tag]).replace_into(table, rows)
609    }
610
611    fn filter_rows_any<'a>(values: impl IntoIterator<Item = &'a dyn Any>) -> Vec<&'a T>
612    where
613        T: 'static,
614    {
615        values
616            .into_iter()
617            .filter_map(|v| v.downcast_ref::<T>())
618            .collect()
619    }
620
621    /// InsertIntoAny:对齐 go `InsertInto(table, value ...interface{})` 的“忽略非预期类型”语义。
622    pub fn insert_into_any<'a>(
623        &self,
624        table: &str,
625        values: impl IntoIterator<Item = &'a dyn Any>,
626    ) -> InsertBuilder
627    where
628        T: 'static,
629    {
630        let rows = Self::filter_rows_any(values);
631        self.insert_into(table, rows)
632    }
633
634    pub fn insert_ignore_into_any<'a>(
635        &self,
636        table: &str,
637        values: impl IntoIterator<Item = &'a dyn Any>,
638    ) -> InsertBuilder
639    where
640        T: 'static,
641    {
642        let rows = Self::filter_rows_any(values);
643        self.insert_ignore_into(table, rows)
644    }
645
646    pub fn replace_into_any<'a>(
647        &self,
648        table: &str,
649        values: impl IntoIterator<Item = &'a dyn Any>,
650    ) -> InsertBuilder
651    where
652        T: 'static,
653    {
654        let rows = Self::filter_rows_any(values);
655        self.replace_into(table, rows)
656    }
657
658    pub fn insert_into_for_tag_any<'a>(
659        &self,
660        table: &str,
661        tag: &str,
662        values: impl IntoIterator<Item = &'a dyn Any>,
663    ) -> InsertBuilder
664    where
665        T: 'static,
666    {
667        let tag: &'static str = Box::leak(tag.to_string().into_boxed_str());
668        let rows = Self::filter_rows_any(values);
669        self.with_tag([tag]).insert_into(table, rows)
670    }
671
672    pub fn insert_ignore_into_for_tag_any<'a>(
673        &self,
674        table: &str,
675        tag: &str,
676        values: impl IntoIterator<Item = &'a dyn Any>,
677    ) -> InsertBuilder
678    where
679        T: 'static,
680    {
681        let tag: &'static str = Box::leak(tag.to_string().into_boxed_str());
682        let rows = Self::filter_rows_any(values);
683        self.with_tag([tag]).insert_ignore_into(table, rows)
684    }
685
686    pub fn replace_into_for_tag_any<'a>(
687        &self,
688        table: &str,
689        tag: &str,
690        values: impl IntoIterator<Item = &'a dyn Any>,
691    ) -> InsertBuilder
692    where
693        T: 'static,
694    {
695        let tag: &'static str = Box::leak(tag.to_string().into_boxed_str());
696        let rows = Self::filter_rows_any(values);
697        self.with_tag([tag]).replace_into(table, rows)
698    }
699
700    pub fn insert_ignore_into<'a>(
701        &self,
702        table: &str,
703        rows: impl IntoIterator<Item = &'a T>,
704    ) -> InsertBuilder
705    where
706        T: 'a,
707    {
708        self.insert_internal(table, rows, InsertVerb::InsertIgnore)
709    }
710
711    pub fn replace_into<'a>(
712        &self,
713        table: &str,
714        rows: impl IntoIterator<Item = &'a T>,
715    ) -> InsertBuilder
716    where
717        T: 'a,
718    {
719        self.insert_internal(table, rows, InsertVerb::Replace)
720    }
721
722    fn insert_internal<'a>(
723        &self,
724        table: &str,
725        rows: impl IntoIterator<Item = &'a T>,
726        verb: InsertVerb,
727    ) -> InsertBuilder
728    where
729        T: 'a,
730    {
731        let mut ib = InsertBuilder::new();
732        ib.set_flavor(self.flavor);
733        match verb {
734            InsertVerb::Insert => {
735                ib.insert_into(table);
736            }
737            InsertVerb::InsertIgnore => {
738                ib.insert_ignore_into(table);
739            }
740            InsertVerb::Replace => {
741                ib.replace_into(table);
742            }
743        }
744
745        let rows: Vec<&T> = rows.into_iter().collect();
746        if rows.is_empty() {
747            // 对齐 go:空 value slice 不会调用 Cols/Values
748            return ib;
749        }
750
751        let fields = self.fields_for_write();
752
753        // 计算列是否应被整体过滤(omitempty 且所有行均为空)
754        let mut nil_cnt = vec![0_usize; fields.len()];
755        for (fi, fm) in fields.iter().enumerate() {
756            let should_omit = self.should_omit_empty(fm);
757            if !should_omit {
758                continue;
759            }
760            for r in &rows {
761                if r.is_empty_field(fm.rust) {
762                    nil_cnt[fi] += 1;
763                }
764            }
765        }
766
767        let mut kept = Vec::<usize>::new();
768        for (i, cnt) in nil_cnt.into_iter().enumerate() {
769            if cnt == rows.len() {
770                continue;
771            }
772            kept.push(i);
773        }
774
775        let cols: Vec<String> = kept
776            .iter()
777            .map(|&i| {
778                let fm = fields[i];
779                let field_alias = self.alias_of(fm);
780                if fm.with_quote {
781                    self.flavor.quote(&field_alias)
782                } else {
783                    field_alias
784                }
785            })
786            .collect();
787        ib.cols(escape_all(cols));
788
789        for r in rows {
790            let mut map = std::collections::HashMap::<&'static str, crate::modifiers::Arg>::new();
791            for (fm, arg) in T::FIELDS.iter().zip(r.values()) {
792                map.insert(fm.rust, arg);
793            }
794            let mut row_args = Vec::new();
795            for &i in &kept {
796                let fm = fields[i];
797                row_args.push(
798                    map.get(fm.rust)
799                        .cloned()
800                        .unwrap_or_else(|| crate::SqlValue::Null.into()),
801                );
802            }
803            ib.values(row_args);
804        }
805
806        ib
807    }
808}
809
810#[derive(Debug, Clone, Copy)]
811enum InsertVerb {
812    Insert,
813    InsertIgnore,
814    Replace,
815}
816
817/// 声明一个可用于 `Struct<T>` 的业务 struct 元数据与取值逻辑。
818///
819/// 用法示例:
820///
821/// ```ignore
822/// #[derive(Default)]
823/// struct User { id: i64, name: String }
824///
825/// sql_builder::sql_struct! {
826///   impl User {
827///     id:  { db: "id", tags: ["pk"], omitempty: [], quote: false, as: None },
828///     name:{ db: "name", tags: [],     omitempty: [""], quote: true,  as: None },
829///   }
830/// }
831/// ```
832#[macro_export]
833macro_rules! sql_struct {
834    (
835        impl $ty:ty {
836            $(
837                $field:ident : { db: $db:literal, $(orig: $orig:literal,)? tags: [ $($tag:literal),* $(,)? ], omitempty: [ $($omit:literal),* $(,)? ], quote: $quote:literal, as: $as:expr }
838            ),* $(,)?
839        }
840    ) => {
841        impl $crate::structs::SqlStruct for $ty {
842            const FIELDS: &'static [$crate::structs::FieldMeta] = &[
843                $(
844                    $crate::structs::FieldMeta{
845                        rust: stringify!($field),
846                        orig: $crate::__sql_struct_orig!(stringify!($field) $(, $orig)?),
847                        db: $db,
848                        as_: $as,
849                        tags: &[ $($tag),* ],
850                        omitempty_tags: &[ $($omit),* ],
851                        with_quote: $quote,
852                    }
853                ),*
854            ];
855
856            fn values(&self) -> Vec<$crate::modifiers::Arg> {
857                vec![
858                    $(
859                        $crate::modifiers::Arg::from(self.$field.clone())
860                    ),*
861                ]
862            }
863
864            fn is_empty_field(&self, rust_field: &'static str) -> bool {
865                match rust_field {
866                    $(
867                        stringify!($field) => $crate::structs::IsEmpty::is_empty_value(&self.$field),
868                    )*
869                    _ => false,
870                }
871            }
872
873            fn addr_cells<'a>(
874                &'a mut self,
875                rust_fields: &[&'static str],
876            ) -> Option<Vec<$crate::scan::ScanCell<'a>>> {
877                let mut out = Vec::with_capacity(rust_fields.len());
878                for &rf in rust_fields {
879                    match rf {
880                        $(
881                            stringify!($field) => {
882                                out.push($crate::scan::ScanCell::from_ptr(std::ptr::addr_of_mut!(self.$field)));
883                            }
884                        )*
885                        _ => return None,
886                    }
887                }
888                Some(out)
889            }
890        }
891    };
892}
893
894/// 宏内部 helper:支持 `orig:` 的可选参数。
895#[doc(hidden)]
896#[macro_export]
897macro_rules! __sql_struct_orig {
898    ($default:expr) => {
899        $default
900    };
901    ($default:expr, $custom:expr) => {
902        $custom
903    };
904}