sqlxplus-cli 0.1.6

Code generator for sqlxplus
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
405
406
407
408
409
# sqlxplus-cli

sqlxplus 的代码生成工具,可以从数据库表结构自动生成 Rust Model 代码,也可以从 Rust Model 代码生成 CREATE TABLE SQL 语句。

## 安装

### 从源码安装

```bash
cargo install --path cli
```

### 从 crates.io 安装(待发布)

```bash
cargo install sqlxplus-cli
```

## 命令

CLI 工具提供两个主要命令:

1. **`generate`** - 从数据库表结构生成 Rust Model 代码
2. **`sql`** - 从 Rust Model 代码生成 CREATE TABLE SQL 语句

## 命令:generate

从数据库表结构自动生成 Rust Model 代码,包含完整的字段宏标注(索引、唯一约束、注释等)。

### 基本用法

```bash
# 交互式选择表
sqlxplus-cli generate -d "mysql://user:pass@localhost/dbname"

# 生成所有表
sqlxplus-cli generate -d "mysql://user:pass@localhost/dbname" --all

# 生成指定表
sqlxplus-cli generate -d "mysql://user:pass@localhost/dbname" -t users -t orders

# 指定输出目录
sqlxplus-cli generate -d "mysql://user:pass@localhost/dbname" -o src/models

# 覆盖已存在的文件
sqlxplus-cli generate -d "mysql://user:pass@localhost/dbname" --overwrite

# 预览生成的代码(不写入文件)
sqlxplus-cli generate -d "mysql://user:pass@localhost/dbname" --dry-run
```

### 支持的数据库

- **MySQL**: `mysql://user:pass@localhost/dbname`
- **PostgreSQL**: `postgres://user:pass@localhost/dbname`
  - 支持 `search_path` 参数:`postgres://user:pass@localhost/dbname?options=-csearch_path%3Dtest`
- **SQLite**: `sqlite://path/to/database.db``sqlite:path/to/database.db`

### 选项说明

- `-d, --database-url`: 数据库连接 URL(必需)
- `-o, --output`: 输出目录,默认为 `models`
- `-t, --tables`: 指定要生成的表名(可多次使用)
- `-a, --all`: 生成所有表,不进行交互式选择
- `--overwrite`: 覆盖已存在的文件
- `--dry-run`: 预览模式,不写入文件
- `--serde`: 生成 serde 序列化/反序列化 derives(默认启用)
- `--derive-crud`: 生成 CRUD derives(默认启用)

### 生成示例

假设数据库中有以下表:

```sql
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
    username VARCHAR(50) NOT NULL COMMENT '用户名',
    email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱地址',
    is_del TINYINT DEFAULT 0 COMMENT '是否删除',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) COMMENT '用户表';
```

运行命令:

```bash
sqlxplus-cli generate -d "mysql://user:pass@localhost/dbname" -t users
```

生成的代码:

```rust
#[derive(Debug, Default, sqlx::FromRow, serde::Serialize, serde::Deserialize, sqlxplus::ModelMeta, sqlxplus::CRUD)]
#[model(table = "users", pk = "id", soft_delete = "is_del", table_comment = "用户表")]
pub struct Users {
    /// 主键 | id (bigint) | 非空
    #[column(primary_key, auto_increment, comment = "主键ID")]
    pub id: Option<i64>,
    
    /// username (varchar(50)) | 非空
    #[column(not_null, length = 50, comment = "用户名")]
    pub username: Option<String>,
    
    /// email (varchar(100)) | 非空
    #[column(not_null, unique, index, length = 100, comment = "邮箱地址")]
    pub email: Option<String>,
    
    /// is_del (tinyint) | 非空
    /// 默认值: 0
    #[column(not_null, default = "0", soft_delete, comment = "是否删除")]
    pub is_del: Option<i16>,
    
    /// created_at (timestamp) | 可空
    /// 默认值: CURRENT_TIMESTAMP
    #[column(default = "CURRENT_TIMESTAMP", comment = "创建时间")]
    pub created_at: Option<chrono::DateTime<chrono::Utc>>,
}
```

### 生成的字段宏标注

从数据库逆向生成的代码会自动包含以下字段宏标注:

- **`primary_key`**: 主键字段
- **`auto_increment`**: 自增字段
- **`not_null`**: 非空字段
- **`default`**: 默认值
- **`length`**: 字段长度(VARCHAR 等类型)
- **`unique`**: 唯一索引字段
- **`index`**: 普通索引字段
- **`soft_delete`**: 逻辑删除字段(自动检测 `is_del`, `is_deleted`, `deleted_at` 等)
- **`comment`**: 字段注释(从数据库获取)

### 表注释支持

如果数据库表有注释,会自动生成到 `#[model(...)]` 属性中:

```rust
#[model(table = "users", pk = "id", soft_delete = "is_del", table_comment = "用户表")]
```

## 命令:sql

从 Rust Model 代码生成 CREATE TABLE SQL 语句,支持 MySQL、PostgreSQL 和 SQLite。

### 基本用法

```bash
# 生成单个文件的 MySQL SQL
sqlxplus-cli sql -m src/models/user.rs -d mysql -o sql/user_mysql.sql

# 批量生成多个文件的 SQL(可多次使用 -m)
sqlxplus-cli sql -m src/models/user.rs -m src/models/order.rs -d mysql -o sql/all_tables.sql

# 扫描目录下所有 .rs 文件并生成 SQL
sqlxplus-cli sql -D src/models -d mysql -o sql/all_tables.sql

# 组合使用:指定文件 + 扫描目录
sqlxplus-cli sql -m src/models/user.rs -D src/models -d mysql -o sql/all_tables.sql

# 生成 PostgreSQL SQL
sqlxplus-cli sql -m src/models/user.rs -d postgres -o sql/user_postgres.sql

# 生成 SQLite SQL
sqlxplus-cli sql -m src/models/user.rs -d sqlite -o sql/user_sqlite.sql

# 输出到标准输出
sqlxplus-cli sql -m src/models/user.rs -d mysql
```

### 选项说明

- `-m, --model`: Rust Model 文件路径(可多次使用以指定多个文件)
- `-D, --dir`: 扫描目录,自动处理目录下所有 `.rs` 文件(可选)
- `-d, --database`: 数据库类型(`mysql`, `postgres`, `sqlite`),默认为 `mysql`
- `-o, --output`: 输出 SQL 文件路径(可选,不指定则输出到标准输出)

### 批量处理说明

当批量处理多个文件时:

- **自动过滤**:不符合 model 格式的文件(没有 `#[model(...)]` 属性的文件)会被自动跳过
- **结果汇总**:处理完成后会显示:
  - ✅ 成功处理的文件列表
  - ⏭️ 忽略的文件列表(没有 model 结构体)
  - ❌ 错误的文件列表(解析错误等)
- **SQL 合并**:所有成功处理的文件的 SQL 会合并到一个输出文件中(如果指定了 `-o`
### 支持的字段宏标注

SQL 生成器支持以下字段宏标注:

- **`primary_key`**: 生成 PRIMARY KEY 约束
- **`auto_increment`**: 生成 AUTO_INCREMENT(MySQL)或 SERIAL(PostgreSQL)
- **`not_null`**: 生成 NOT NULL 约束
- **`default`**: 生成 DEFAULT 值
- **`length`**: 指定字段长度(如 VARCHAR(255))
- **`unique`**: 生成唯一索引
- **`index`**: 生成普通索引
- **`combine_index`**: 生成联合索引(格式:`combine_index = "idx_name:order"`- **`soft_delete`**: 逻辑删除字段(不影响 SQL 生成)
- **`comment`**: 生成字段注释(数据库特定语法)

### SQL 生成示例

假设有以下 Rust Model:

```rust
#[derive(Debug, Default, sqlx::FromRow, sqlxplus::ModelMeta, sqlxplus::CRUD)]
#[model(table = "user", pk = "id", soft_delete = "is_del", table_comment = "用户表")]
pub struct User {
    #[column(primary_key, auto_increment, comment = "主键ID")]
    pub id: Option<i64>,
    
    #[column(not_null, default = "1", comment = "系统类型")]
    pub system_type: Option<i16>,
    
    #[column(index, length = 255, comment = "用户名")]
    pub username: Option<String>,
    
    #[column(unique, index, length = 255, comment = "邮箱地址")]
    pub email: Option<String>,
    
    #[column(not_null, default = "0", index, soft_delete, comment = "是否删除")]
    pub is_del: Option<i16>,
}
```

生成的 MySQL SQL:

```sql
CREATE TABLE `user` (
    `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `system_type` TINYINT NOT NULL DEFAULT 1 COMMENT '系统类型',
    `username` VARCHAR(255) COMMENT '用户名',
    `email` VARCHAR(255) COMMENT '邮箱地址',
    `is_del` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_user_email` (`email`)
) COMMENT '用户表';

CREATE INDEX `idx_user_username` ON `user` (`username`);
CREATE INDEX `idx_user_is_del` ON `user` (`is_del`);
```

生成的 PostgreSQL SQL:

```sql
CREATE TABLE "user" (
    "id" BIGSERIAL NOT NULL,
    "system_type" SMALLINT NOT NULL DEFAULT 1,
    "username" VARCHAR(255),
    "email" VARCHAR(255),
    "is_del" SMALLINT NOT NULL DEFAULT 0,
    PRIMARY KEY ("id"),
    CONSTRAINT "uk_user_email" UNIQUE ("email")
);

CREATE INDEX "idx_user_username" ON "user" ("username");
CREATE INDEX "idx_user_is_del" ON "user" ("is_del");

COMMENT ON COLUMN "user"."id" IS '主键ID';
COMMENT ON COLUMN "user"."system_type" IS '系统类型';
COMMENT ON COLUMN "user"."username" IS '用户名';
COMMENT ON COLUMN "user"."email" IS '邮箱地址';
COMMENT ON COLUMN "user"."is_del" IS '是否删除';
COMMENT ON TABLE "user" IS '用户表';
```

## 特性

### 代码生成(generate 命令)

- ✅ 自动检测主键字段
- ✅ 自动检测逻辑删除字段(`is_del`, `is_deleted`, `deleted_at` 等)
- ✅ 支持 MySQL、PostgreSQL、SQLite
- ✅ 支持 PostgreSQL 的 `search_path` 参数
- ✅ 交互式表选择
- ✅ 批量生成多个表
- ✅ 自动生成 `mod.rs` 模块文件
- ✅ 类型映射(SQL 类型 → Rust 类型)
- ✅ 自动生成字段宏标注(索引、唯一约束、注释等)
- ✅ 支持表注释和字段注释
- ✅ 自动检测索引和唯一约束

### SQL 生成(sql 命令)

- ✅ 从 Rust Model 生成 CREATE TABLE SQL
- ✅ 支持 MySQL、PostgreSQL、SQLite 三种数据库
- ✅ 生成数据库特定的 SQL 语法
- ✅ 支持字段注释和表注释
- ✅ 支持单独索引和联合索引
- ✅ 支持唯一索引和普通索引
- ✅ 自动处理数据库类型差异

## 类型映射

### SQL → Rust 类型映射

| SQL 类型                | Rust 类型                        |
| ----------------------- | -------------------------------- |
| `BIGINT`, `BIGSERIAL`   | `i64`                            |
| `INT`, `INTEGER`, `SERIAL` | `i32`                          |
| `SMALLINT`, `TINYINT`, `SMALLSERIAL` | `i16`                    |
| `VARCHAR`, `TEXT`, `CHARACTER VARYING` | `String`                  |
| `DECIMAL`, `DOUBLE`, `NUMERIC` | `f64`                      |
| `BOOLEAN`, `BOOL`, `BIT` | `bool`                           |
| `DATE`                  | `chrono::NaiveDate`            |
| `DATETIME`              | `chrono::NaiveDateTime`         |
| `TIMESTAMP` (MySQL)     | `chrono::DateTime<chrono::Utc>` |
| `TIMESTAMP WITH TIME ZONE` (PostgreSQL) | `chrono::DateTime<chrono::Utc>` |
| `TIMESTAMP WITHOUT TIME ZONE` (PostgreSQL) | `chrono::NaiveDateTime` |
| `BLOB`, `BYTEA`         | `Vec<u8>`                        |
| `JSON`, `JSONB`         | `serde_json::Value`              |
| `UUID`                  | `uuid::Uuid`                     |

### 类型选择规则

- 如果字段为 `NULLable`,生成 `Option<T>`
- 如果字段有默认值,生成 `Option<T>`(因为插入时可以不手动赋值)
- 如果字段为 `NOT NULL` 且无默认值,生成 `T`

## 使用场景

### 场景 1:从数据库生成 Model 代码

适用于已有数据库表,需要快速生成对应的 Rust Model 代码:

```bash
# 从 PostgreSQL 数据库生成代码
sqlxplus-cli generate \
  -d "postgres://user:pass@localhost/dbname?options=-csearch_path%3Dtest" \
  -t users \
  -o src/models \
  --overwrite
```

### 场景 2:从 Model 代码生成 SQL

适用于先定义 Rust Model,然后生成数据库建表 SQL:

```bash
# 生成 MySQL 建表 SQL
sqlxplus-cli sql \
  -m src/models/user.rs \
  -d mysql \
  -o migrations/001_create_user.sql

# 批量生成多个表的 SQL
sqlxplus-cli sql \
  -m src/models/user.rs \
  -m src/models/order.rs \
  -d mysql \
  -o migrations/001_create_tables.sql

# 扫描整个目录生成 SQL
sqlxplus-cli sql \
  -D src/models \
  -d mysql \
  -o migrations/001_create_all_tables.sql
```

### 场景 3:数据库迁移

结合使用两个命令,实现数据库迁移:

1. 从旧数据库生成 Model 代码
2. 修改 Model 代码
3. 生成新的 SQL 迁移脚本

## 注意事项

1. **PostgreSQL search_path**: 如果使用自定义 schema,需要在连接 URL 中指定:
   ```
   postgres://user:pass@localhost/dbname?options=-csearch_path%3Dyour_schema
   ```

2. **字段注释**: 
   - MySQL: 注释直接包含在 CREATE TABLE 语句中
   - PostgreSQL: 使用 `COMMENT ON` 语句单独添加
   - SQLite: 不支持注释,但会在生成的 SQL 中使用 SQL 注释(`--`
3. **索引生成**:
   - 唯一索引会同时生成 `unique``index` 属性
   - 联合索引使用 `combine_index = "idx_name:order"` 格式

4. **默认值处理**:
   - PostgreSQL 的序列(`nextval`)会被自动识别为 `auto_increment`
   - 空字符串默认值会正确处理

## 常见问题

### Q: 如何生成包含所有字段宏标注的代码?

A: 使用 `generate` 命令从数据库生成,会自动包含所有字段宏标注(索引、唯一约束、注释等)。

### Q: 生成的代码与手动编写的代码有什么区别?

A: 主要区别在于:
- 字段类型注释使用数据库实际类型名称(如 PostgreSQL 的 `character varying` vs MySQL 的 `varchar`- 属性顺序可能略有不同(不影响功能)

### Q: 如何支持自定义 schema?

A: 对于 PostgreSQL,在连接 URL 中使用 `search_path` 参数即可。

## License

MIT OR Apache-2.0