sql_tool_kit 0.1.2

合并 sql_tool_core 和 sql_tool_macros 并一起导出,后续新增的功能都将从这个库中导出
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# SQL 语句辅助生成器


`sql_tool_kit` 库提供了一系列派生宏(`GenFields`, `GenSelect`, `GenValues`, `GenSet`, `GenWhere`),
用于自动派生与 SQL 语句构建相关的 trait 实现。这些宏简化了根据结构体字段生成 SQL 语句的过程。

##  派生宏介绍


### `#[derive(GenFields)]``#[derive(GenSelect)]`


这两个派生宏功能相似,主要区别在于命名。它们都处理字段及其上的 `#[field(...)]` 和 `#[select(...)]` 宏。

使用方法:StructName::generate_fields_clause() 和 StructName::generate_select_clause() 
分别生成字段名称数组(如 `["field1", "field2", ...]`)。
应用场景:通过 `.join(", ")` 方法将返回的数组拼接为字符串,可用于 SQL 语句的 `SELECT` 和 `INSERT` 部分。

字段宏参数:
- `ignore` - 忽略该字段
- `rename` - 字段重命名

导入
```rust
/// 导入 GenFields 和对应实现的 trait
use sql_tool_kit::{GenFields, FieldsAttributeMacro};
/// 导入 GenSelect
use sl_tool_kit::{GenSelect, SelectAttributeMacro};
```

使用
```rust
#[derive(GenFields)]

pub struct FieldsStruct {
    field: i32,
    #[field(ignore)]
    field2: i32,
    #[field(rename = "rename_field")]
    field3: i32,
}
FieldsStruct::generate_fields_clause(); // 输出: [“field1", "rename_field"]

#[derive(GenFields)]

pub struct SelectStruct {
    field: i32,
    #[field(ignore)]
    field2: i32,
    #[field(rename = "rename_field")]
    field3: i32,
}
SelectStruct::generate_fields_clause(); // 输出: [“field1", "rename_field"]
```

### `#[derive(GenValues)]`


`GenValues` 生成用于 insert 语句中 values 部分,通过 `StructName::generate_values_clause()` 得到
`["$1", "$2", ...]`。

需要在结构上使用宏 `#[config(...)]` 来配置序列化的方式:

宏参数:
- `#[config(...)]`: 设置全局配置。
  - `database` - 指定生成的数据库类型,目前支持 `postgresql` `mysql` `mariadb` `sqlite` `mssql`
  - `index` - 指定开始的序列,仅 `postgresql` `mssql` 上有效

`#[value(...)]` 接受的参数:
- `ignore` - 忽略该字段
  - `index` - 设置当前值的index,当设置了这个参数后,全局的 index 不会加一
  - `value` - 直接替换当前的 `${index}` ,当设置了这个参数后,全局的 index 不会加一
      - 例如:`#[value(value = "true")]` => ["$1", "true",...]

使用方式
```rust
use sql_tool_kit::{GenValues, ValuesAttributeMacro};

#[derive(GenValues)]

#[config(database = "postgres")]

pub struct ValuesStruct {
  #[value(ignore)]
  field1: i32,
  #[value(index = 2)]
  field2: i32,
  #[value(value = "20")]
  field3: i32,
  #[value(value = "{index}::bit(4)")]
  field4: i32,
}

ValuesStruct::generate_values_clause(); // 输出:["$2", "20", "$1::bit(4)"]
```

### `#[derive(GenWhere)]`


用于生成 SQL `WHERE` 语句部分。此宏依赖于 `WhereAttributeMacro` trait。
使用方法 `where_data.generate_where_clause()` 会返回一个字段和条件组成的字符串数组。
使用方法 `where_data.generate_where_clause_with_index(index)` 可以设置开始初始的 index 值。

使用方式

```rust
use sql_tool_kit::{GenWhere, WhereAttributeMacro};

#[derive(GenWhere)]

#[config(database = "postgres")]

pub struct WhereStruct {
  #[r#where()]
  field1: i32,
  field2: i32,
  #[r#where(rename = "rename_filed")]
  field3: i32,
  #[r#where(condition = ">=")]
  field4: i32,
  #[r#where(condition_all = "{name} not null")]
  field5: i32,
  #[r#where(value = "{index}::bit(4)")]
  field6: i32,
  #[r#where(index = 1)]
  field7: i32,
}

let data = WhereStruct { ... };

data.generate_where_clause(); // 输出:["field1 = $1", "rename_field = $2", "field4 >= $3", "field5 not null", "field5 = $4::bit(4)", "field7 = $1"]
```

宏参数:
- `#[config(...)]`: 设置全局配置。
  - `database`: 指定数据库类型,影响占位符格式(支持 postgres, mysql, sqlite, mariadb, mssql)。
  - `index`: 设置占位符的起始索引。
  - `ignore_none`: 是否忽略 `Option::None` 值,默认为 `true`  - `ignore_no_macro_where`: 是否忽略没有 `#[r#where(...)]` 宏的字段,默认值为 `true`, 为 `true` 时可配合 `GenSet` 宏使用。

- `#[r#where(...)]`: 字段级别宏,用于自定义字段在 `WHERE` 语句中的表现。
  - `ignore`: 忽略该字段。
  - `rename`: 字段重命名,接受字符串类型。
  - `condition`: 指定字段的比较条件,默认值为 ”=“,如果该值设置为空及 "", 会报错。
  - `condition_all`: 应用于所有字段的通用条件,缺省值为 `"{name} {condition} {index}"`    - `{name}`: 字段名称或 `rename` 指定的名称。
    - `{condition}`: `condition` 参数指定的比较条件。如果 `condition_all`    - `{index}`: `index` 参数指定的占位符索引。如果字段未设置 `index`,则使用全局 `index`  - `ignore_none`: 当字段为 `Option::None` 时是否忽略,接受布尔类型。
  - `value`: 自定义字段的值,接受字符串类型。
  - `index`: 自定义占位符序号(如果数据库支持),接受整型。

字段宏属性优先级:
`ignore` > `ignore_none` > `condition_all` > `rename` = `condition` = `value` > `index`

### `#[derive(GenSet)]`


用于生成 SQL `UPDATE` 语句中的 `SET` 部分。它依赖于 `SetAttributeMacro` trait。
例如,`update table_name set field1 = $1, field2 = $2 ... where ...`
使用方法 `update_data.generate_set_clause()` 返回值类似于 `["field1 = $1", "field2 = $2", ...]`。
为了方便接入后续的 where 语句,在 `#[set(...)]` 添加了 `where` 参数,它可以为 `where` 或 `where = "..."`
通过方法 `generate_set_and_where_clause()` 返回值一个元组 `(["field1 = $1", ...], ["field5 = $5", "field6 > $6", ...])`,
第一个为 set 的值,第二个为 where 的值

使用方式

```rust
use sql_tool_kit::{GenSet, SetAttributeMacro};

#[derive(GenSet)]

#[config(database = "postgres")]

pub struct SetStruct {

  #[set(r#where)]
  pub id: i32,
  pub title: Option<String>,
  pub subtitle: Option<String>,
  pub image_url: Option<String>,
  pub link_url: Option<String>,
  pub description: Option<String>,
  #[set(value = "now()")]
  pub updated_at: Option<()>,
}

let data = UpdateForm {
  id: 1,
  title: Some("这是标题".to_string()),
  subtitle: None,
  image_url: None,
  link_url: None,
  description: Some("这是描述".to_string()),
  updated_at: Some(()),
};
let (set_values, where_value) = data.generate_set_and_where_clause(); // ["title = $1", "description = $2", "updated_at = now()"]
```

宏参数:
- `#[config(...)]`: 设置一些配置。
  - `database`: 指定数据库类型,影响占位符的格式(支持 mysql, postgres, sqlite, mariadb, mssql)。
  - `index`: 设置占位符的起始索引。
  - `ignore_none`: 是否忽略 `Option::None` 值,默认为 `true`  - `ignore_no_macro_set`: 默认忽略没有 `#[set(...)]` 宏的字段,为 `true` 时配合 `GenWhere` 宏使用。
  - `ignore_set_and_where`: 当 `#[set(...)]`存在 `where` 参数是,会忽略 `set` 值,默认为 `false`

- `#[set(...)]`: 字段级别的宏,用于自定义字段在生成的 `SET` 语句中的表现。
  - `ignore`: 忽略该字段。
  - `r#where`: 将该字段设置为 where,有多种使用方式。1. `#[set(r#where)]` `#[set(r#where = "{field = {index}")]`
  - `ignore_none`: 当字段为 `Option::None` 时是否忽略,接受布尔类型。
  - `ignore_set`: 在 set 上忽略该字段。
  - `rename`: 字段重命名,接受字符串类型。
  - `condition`: 当设置 `r#where` 时生效
  - `value`: 自定义字段的值,接受字符串类型。
  - `index`: 自定义占位符序号(如果数据库支持),接受整型。

宏的优先级:`ignore` > `ignore_none` > `r#where` = `ignore_set` > `rename` = `value` = `condition` > `index`



## 使用示例


### insert 语句

```rust
#[derive(GenFields, GenValues)]

#[config(database = "postgres")]

pub struct InsertForm {
  /// 标题
  pub title: String,
  /// 副标题
  #[value(index = 1)]
  pub subtitle: Option<String>,
  /// 图片地址
  pub image_url: String,
  /// 跳转链接
  pub link_url: Option<String>,
  /// 开始时间
  #[value(value = "now()")]
  pub start_time: String,
  /// 结束时间,如果没有结束时间,该广告会一直显示下去
  pub end_time: String,
  /// 描述
  pub description: Option<String>,
  /// 类型
  #[field(rename = "type")]
  pub ty: i32,
  /// 排序
  pub sort: i32,
}

fn main() {
    // 结果:insert into table_name (title, subtitle, image_url, link_url, start_time, end_time, description, type, sort) values ($1, $1, $2, $3, now(), $4, $5, $6, $7)
    let query = format!("insert into table_name ({}) values ({})", InsertForm::generate_fields_clause().join(", "), InsertForm::generate_values_clause().join(", "));
}

```

### update 更新语句


```rust
#[derive(GenSet)]

// ignore_no_macro_set = false, 设置不忽略没有设置 #[set()] 字段的结构
// index = 1, 设置 index 从 1 开始,默认值:如果不设置,index 则默认为 1
#[config(database = "postgres", index = 1, ignore_no_macro_set = false)] 

pub struct UpdateForm {
    #[set(r#where)]
    pub id: i32,
    pub title: Option<String>,
    pub subtitle: Option<String>,
    pub image_url: Option<String>,
    pub link_url: Option<String>,
    pub description: Option<String>,
    #[set(value = "now()")]
    pub updated_at: Option<()>,
}

async fn main() -> Result<()> {
    let data = UpdateForm {
        id: 1,
        title: Some("这是标题".to_string()),
        subtitle: None,
        image_url: None,
        link_url: None,
        description: Some("这是描述".to_string()),
        updated_at: Some(()),
    };
    let (set_values, where_value) = data.generate_set_and_where_clause();
    // 同等于 update table_name set title = $1, description = $2, updated_at = now() where id = $3
    let query = format!("update table_name set {} where {}", set_values.join(", "), where_value.join(" AND "));
    
    let mut sql = sqlx::query::<_, QueryRow>(&query);
    if data.title.is_some() {
        sql = sql.bind(data.title);
    }
    if data.subtitle.is_some() {
        sql = sql.bind(data.subtitle);
    }
    if data.image_url.is_some() {
        sql = sql.bind(data.image_url);
    }
    if data.link_url.is_some() {
        sql = sql.bind(data.link_url);
    }
    if data.description.is_some() {
        sql = sql.bind(data.description);
    }
    
    let rows_affected = sql.bind(id).execute(pool).await?.rows_affected;
}
```

### select 查询

```rust
use sql_tool_kit::*;

#[derive(GenWhere)]

#[config(database = "postgres")]

pub struct QueryForm {
    /// 关键字, 可以在 标题,副标题,描述上查询
    #[r#where(condition_all = "title like {index} AND subtitle like {index} AND description like {index}")]
    pub keyword: Option<String>,
    /// 开始时间
    #[r#where(condition = ">=")]
    pub start_time: Option<String>,
    /// 结束时间
    #[r#where(condition = "<=")]
    pub end_time: Option<String>,
    /// 广告类型
    #[r#where()]
    pub ty: i32,
    /// 页码信息
    #[r#where(ignore)]
    pub page_info: usize,
}

#[derive(GenSelect)]

pub struct QueryRow {
    /// 标题
    pub title: String,
    /// 副标题
    pub subtitle: Option<String>,
    /// 图片地址
    pub image_url: String,
    /// 跳转链接
    pub link_url: Option<String>,
    /// 开始时间
    pub start_time: String,
    /// 结束时间,如果没有结束时间,该广告会一直显示下去
    pub end_time: String,
    /// 描述
    pub description: Option<String>,
    /// 类型
    #[select(rename = "type")]
    pub ty: i32,
    /// 排序
    pub sort: i32,
}

async fn main() -> Result<()> {
    let data = QueryForm {
        keyword: Some("这是标题".to_string()),
        start_time: Some("2024/12/12".to_string()),
        end_time: None,
        ty:1,
        page_info: 0,
    };
    
    // 同等于:select title, subtitle, image_url, link_url, start_time, end_time, description, type, sort from table_name where title like $1 AND subtitle like $1 AND description like $1 AND start_time >= $2 AND ty = $3
    let query = format!("select {} from table_name where {}", QueryRow::generate_select_clause().join(", "), data.generate_where_clause().join(" AND "));

    let mut sql = sqlx::query::<_, QueryRow>(&query);
    if data.keyword.is_some() {
        sql = sql.bind(data.keyword);
    }
    if data.start_time.is_some() {
        sql = sql.bind(data.start_time);
    }
    if data.end_time.is_some() {
        sql = sql.bind(data.end_time);
    }
    let result = sql.bind(ty).fetch_all(pool).await?;
}
```

### delete 语句

```rust

#[derive(GenWhere)]

#[config(database = "postgres")]

pub struct DeleteForm {
    #[r#where()]
    pub id: i32,
    #[r#where()]
    pub title: Option<String>,
    #[r#where(condition = ">=")]
    pub start_time: Option<String>,
    #[r#where(condition = "<=")]
    pub end_time: Option<String>,
}

fn main() {
    let data = DeleteForm {
        id: 1,
        title: None,
        start_time: Some("2025/12/12".to_string()),
        end_time: Some("2024/12/12".to_string()),
    };

    // 输出:delete table_name where id = $1 AND start_time >= $2 AND end_time <= $3
    let query = format!("delete table_name where {}", data.generate_where_clause().join(" AND "));
}
```

## 后续可能的扩展


1. 完整的 sql 语句生成
2. 优化 sqlx 的绑定值步骤