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