mm_mysql 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -1
- package/db.js +118 -37
- package/package.json +1 -1
- package/test_create_table.js +193 -0
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# mm_mysql
|
|
2
|
-
|
|
2
|
+
一个简洁、高效的Node.js MySQL操作库,支持async/await语法,提供直观的API和强大的功能扩展。
|
|
3
3
|
|
|
4
4
|
[](https://www.npmjs.com/package/mm_mysql)
|
|
5
5
|
[](https://gitee.com/qiuwenwu91/mm_mysql/issues)
|
|
@@ -37,6 +37,16 @@ const db = mysql.db().new('users', 'id');
|
|
|
37
37
|
await mysql.close();
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
## 主要特性
|
|
41
|
+
|
|
42
|
+
- 支持async/await语法,避免回调地狱
|
|
43
|
+
- 提供简洁直观的API接口
|
|
44
|
+
- 内置事务支持
|
|
45
|
+
- 自动类型推断的表创建功能(支持int、float、varchar、text、datetime、date、time、boolean等多种数据类型)
|
|
46
|
+
- 灵活的查询条件支持
|
|
47
|
+
- 支持连接池管理
|
|
48
|
+
- 调试模式支持
|
|
49
|
+
|
|
40
50
|
## API文档
|
|
41
51
|
|
|
42
52
|
### Mysql类
|
|
@@ -118,6 +128,21 @@ const config = {
|
|
|
118
128
|
|
|
119
129
|
### DB类方法
|
|
120
130
|
|
|
131
|
+
#### createTable(model, options)
|
|
132
|
+
自动创建数据库表,支持多种数据类型推断
|
|
133
|
+
- `model` {Object} 模型对象,包含表名和字段定义
|
|
134
|
+
- `table` {String} 表名
|
|
135
|
+
- `fields` {Object} 字段定义,支持以下数据类型:
|
|
136
|
+
- 数字类型(自动推断为INT/FLOAT)
|
|
137
|
+
- 字符串类型(自动推断为VARCHAR/TEXT/DATE/DATETIME/TIME)
|
|
138
|
+
- 布尔类型(自动推断为TINYINT(1))
|
|
139
|
+
- Date对象(自动推断为DATETIME)
|
|
140
|
+
- 时间戳(自动推断为DATETIME)
|
|
141
|
+
- `options` {Object} 选项配置(可选)
|
|
142
|
+
- `primary_key` {String} 主键字段名(可选)
|
|
143
|
+
- `if_not_exists` {Boolean} 是否仅当表不存在时创建(可选,默认为true)
|
|
144
|
+
- 返回:{Promise<Object>} 执行结果
|
|
145
|
+
|
|
121
146
|
#### new(table, key)
|
|
122
147
|
创建数据库表管理器
|
|
123
148
|
- `table` {String} 表名
|
|
@@ -172,6 +197,61 @@ const config = {
|
|
|
172
197
|
|
|
173
198
|
### 使用示例
|
|
174
199
|
|
|
200
|
+
#### 表创建示例
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
const { Mysql } = require('mm_mysql');
|
|
204
|
+
|
|
205
|
+
async function createTableExample() {
|
|
206
|
+
const mysql = new Mysql({
|
|
207
|
+
host: "localhost",
|
|
208
|
+
user: "root",
|
|
209
|
+
password: "123456",
|
|
210
|
+
database: "test"
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await mysql.open();
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
// 创建数据库表管理器
|
|
217
|
+
const db = mysql.db();
|
|
218
|
+
|
|
219
|
+
// 定义表模型
|
|
220
|
+
const userModel = {
|
|
221
|
+
table: 'users',
|
|
222
|
+
fields: {
|
|
223
|
+
id: 1, // 整数类型 -> INT
|
|
224
|
+
username: 'test', // 字符串类型 -> VARCHAR(255)
|
|
225
|
+
email: 'test@example.com', // 字符串类型 -> VARCHAR(255)
|
|
226
|
+
age: 25, // 整数类型 -> INT
|
|
227
|
+
salary: 5000.50, // 浮点数类型 -> FLOAT
|
|
228
|
+
active: true, // 布尔类型 -> TINYINT(1)
|
|
229
|
+
birth_date: '2000-01-01', // 日期字符串 -> DATE
|
|
230
|
+
bio: '这是一个很长的个人简介...', // 长字符串 -> TEXT
|
|
231
|
+
created_at: '2023-01-01 12:00:00', // 日期时间字符串 -> DATETIME
|
|
232
|
+
login_time: new Date(), // Date对象 -> DATETIME
|
|
233
|
+
last_active: Date.now() // 时间戳 -> DATETIME
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// 创建表
|
|
238
|
+
const result = await db.createTable(userModel, {
|
|
239
|
+
primary_key: 'id',
|
|
240
|
+
if_not_exists: true
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
console.log('表创建成功:', result);
|
|
244
|
+
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('创建表失败:', error);
|
|
247
|
+
} finally {
|
|
248
|
+
await mysql.close();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
createTableExample().catch(console.error);
|
|
253
|
+
```
|
|
254
|
+
|
|
175
255
|
#### 基本查询操作
|
|
176
256
|
|
|
177
257
|
```javascript
|
|
@@ -302,7 +382,28 @@ transactionExample().catch(console.error);
|
|
|
302
382
|
- `_not`: 不等于
|
|
303
383
|
- `_has`: IN查询
|
|
304
384
|
- `_like`: LIKE查询
|
|
385
|
+
6. createTable方法会根据字段值自动推断数据类型,如需更精确的类型控制,建议直接使用SQL语句
|
|
386
|
+
|
|
387
|
+
## 更新日志
|
|
388
|
+
|
|
389
|
+
### v2.2.0
|
|
390
|
+
- 新增createTable方法,支持多种数据类型自动推断
|
|
391
|
+
- 支持boolean类型自动转换为TINYINT(1)
|
|
392
|
+
- 支持date字符串自动识别为DATE类型
|
|
393
|
+
- 支持Date对象自动识别为DATETIME类型
|
|
394
|
+
- 支持时间戳自动识别为DATETIME类型
|
|
305
395
|
|
|
306
396
|
## 许可证
|
|
307
397
|
|
|
308
398
|
ISC License
|
|
399
|
+
|
|
400
|
+
## 贡献
|
|
401
|
+
|
|
402
|
+
欢迎提交Issue和Pull Request!
|
|
403
|
+
|
|
404
|
+
## 联系方式
|
|
405
|
+
|
|
406
|
+
如有问题或建议,请通过以下方式联系:
|
|
407
|
+
|
|
408
|
+
- 项目地址:https://gitee.com/qiuwenwu91/mm_mysql
|
|
409
|
+
- 邮箱:qiuwenwu91@163.com
|
package/db.js
CHANGED
|
@@ -23,7 +23,7 @@ class DB extends Sql {
|
|
|
23
23
|
* 获取上级
|
|
24
24
|
* @return {Object} 返回上级管理器
|
|
25
25
|
*/
|
|
26
|
-
this.parent = function() {
|
|
26
|
+
this.parent = function () {
|
|
27
27
|
return mysql;
|
|
28
28
|
};
|
|
29
29
|
}
|
|
@@ -36,7 +36,7 @@ class DB extends Sql {
|
|
|
36
36
|
* @param {String} message - 错误信息
|
|
37
37
|
* @returns {Promise<Error>}
|
|
38
38
|
*/
|
|
39
|
-
DB.prototype._createTimeoutPromise = function(timeout, message) {
|
|
39
|
+
DB.prototype._createTimeoutPromise = function (timeout, message) {
|
|
40
40
|
return new Promise((_, reject) => {
|
|
41
41
|
setTimeout(() => {
|
|
42
42
|
reject(new Error(message));
|
|
@@ -48,7 +48,7 @@ DB.prototype._createTimeoutPromise = function(timeout, message) {
|
|
|
48
48
|
* 获取数据库名
|
|
49
49
|
* @return {String} 数据库
|
|
50
50
|
*/
|
|
51
|
-
DB.prototype.database = function() {
|
|
51
|
+
DB.prototype.database = function () {
|
|
52
52
|
return this.parent().config.database;
|
|
53
53
|
};
|
|
54
54
|
|
|
@@ -58,7 +58,7 @@ DB.prototype.database = function() {
|
|
|
58
58
|
* @param {String} key 键名
|
|
59
59
|
* @return {Object} 返回管理器
|
|
60
60
|
*/
|
|
61
|
-
DB.prototype.new = function(table, key) {
|
|
61
|
+
DB.prototype.new = function (table, key) {
|
|
62
62
|
const db = this.parent().db();
|
|
63
63
|
db.table = table;
|
|
64
64
|
if (key) {
|
|
@@ -77,7 +77,7 @@ DB.prototype.new = function(table, key) {
|
|
|
77
77
|
* @param {Number} timeout 超时时间(毫秒)
|
|
78
78
|
* @returns {Promise<Object>} 查询结果
|
|
79
79
|
*/
|
|
80
|
-
DB.prototype.run = async function(sql, params = [], timeout = 30000) {
|
|
80
|
+
DB.prototype.run = async function (sql, params = [], timeout = 30000) {
|
|
81
81
|
if (!this._mysql) {
|
|
82
82
|
throw new Error('MySQL实例未初始化');
|
|
83
83
|
}
|
|
@@ -103,7 +103,7 @@ DB.prototype.run = async function(sql, params = [], timeout = 30000) {
|
|
|
103
103
|
* @param {Number} timeout 超时时间(毫秒)
|
|
104
104
|
* @returns {Promise<Object>} 执行结果
|
|
105
105
|
*/
|
|
106
|
-
DB.prototype.exec = async function(sql, timeout = 60000) {
|
|
106
|
+
DB.prototype.exec = async function (sql, timeout = 60000) {
|
|
107
107
|
if (!this._mysql) {
|
|
108
108
|
throw new Error('MySQL实例未初始化');
|
|
109
109
|
}
|
|
@@ -124,7 +124,7 @@ DB.prototype.exec = async function(sql, timeout = 60000) {
|
|
|
124
124
|
* @param {Number} timeout 超时时间(毫秒)
|
|
125
125
|
* @returns {Promise<Object>} 数据库连接对象
|
|
126
126
|
*/
|
|
127
|
-
DB.prototype.getConn = async function(timeout = 60000) {
|
|
127
|
+
DB.prototype.getConn = async function (timeout = 60000) {
|
|
128
128
|
if (!this._mysql) {
|
|
129
129
|
throw new Error('MySQL实例未初始化');
|
|
130
130
|
}
|
|
@@ -145,7 +145,7 @@ DB.prototype.getConn = async function(timeout = 60000) {
|
|
|
145
145
|
* @param {Number} timeout 超时时间(毫秒)
|
|
146
146
|
* @return {Promise<Object>} 执行结果
|
|
147
147
|
*/
|
|
148
|
-
DB.prototype.start = async function(identifier = "point_1", timeout = 15000) {
|
|
148
|
+
DB.prototype.start = async function (identifier = "point_1", timeout = 15000) {
|
|
149
149
|
this.task = 1;
|
|
150
150
|
try {
|
|
151
151
|
return await this.exec("SET AUTOCOMMIT=0;BEGIN;", timeout);
|
|
@@ -161,7 +161,7 @@ DB.prototype.start = async function(identifier = "point_1", timeout = 15000) {
|
|
|
161
161
|
* @param {Number} timeout 超时时间(毫秒)
|
|
162
162
|
* @return {Promise<Object>} 执行结果
|
|
163
163
|
*/
|
|
164
|
-
DB.prototype.commit = async function(timeout = 15000) {
|
|
164
|
+
DB.prototype.commit = async function (timeout = 15000) {
|
|
165
165
|
if (this.task !== 1) {
|
|
166
166
|
$.log.warn("[DB] [commit] 没有活跃的事务可提交");
|
|
167
167
|
return 0;
|
|
@@ -190,7 +190,7 @@ DB.prototype.commit = async function(timeout = 15000) {
|
|
|
190
190
|
/**
|
|
191
191
|
* 事务结束
|
|
192
192
|
*/
|
|
193
|
-
DB.prototype.end = function() {
|
|
193
|
+
DB.prototype.end = function () {
|
|
194
194
|
// 确保事务状态被重置
|
|
195
195
|
this.task = 0;
|
|
196
196
|
this.task_sql = '';
|
|
@@ -202,7 +202,7 @@ DB.prototype.end = function() {
|
|
|
202
202
|
* @param {Number} timeout 超时时间(毫秒)
|
|
203
203
|
* @return {Promise<Object>} 执行结果
|
|
204
204
|
*/
|
|
205
|
-
DB.prototype.back = async function(identifier = "point_1", timeout = 15000) {
|
|
205
|
+
DB.prototype.back = async function (identifier = "point_1", timeout = 15000) {
|
|
206
206
|
if (this.task !== 1) {
|
|
207
207
|
$.log.warn("[DB] [back] 没有活跃的事务可回滚");
|
|
208
208
|
this.task = 0;
|
|
@@ -231,7 +231,7 @@ DB.prototype.back = async function(identifier = "point_1", timeout = 15000) {
|
|
|
231
231
|
* @param {Number} timeout 超时时间(毫秒)
|
|
232
232
|
* @return {Promise|Array} 表名数组
|
|
233
233
|
*/
|
|
234
|
-
DB.prototype.tables = async function(table, timeout = 15000) {
|
|
234
|
+
DB.prototype.tables = async function (table, timeout = 15000) {
|
|
235
235
|
try {
|
|
236
236
|
const list = await this.run("show tables", [], timeout);
|
|
237
237
|
const key = 'Tables_in_' + this.database();
|
|
@@ -261,7 +261,7 @@ DB.prototype.tables = async function(table, timeout = 15000) {
|
|
|
261
261
|
* @param {Number} timeout 超时时间(毫秒)
|
|
262
262
|
* @return {Promise|Array} 字段信息列表
|
|
263
263
|
*/
|
|
264
|
-
DB.prototype.fields = async function(table, field_name, timeout = 15000) {
|
|
264
|
+
DB.prototype.fields = async function (table, field_name, timeout = 15000) {
|
|
265
265
|
try {
|
|
266
266
|
const targetTable = table || this.table;
|
|
267
267
|
if (!targetTable) {
|
|
@@ -297,7 +297,7 @@ DB.prototype.fields = async function(table, field_name, timeout = 15000) {
|
|
|
297
297
|
* @param {Number} timeout 超时时间(毫秒)
|
|
298
298
|
* @return {Promise|Number} 创建成功返回1,失败返回0
|
|
299
299
|
*/
|
|
300
|
-
DB.prototype.addTable = async function(table, field, type = 'int', auto = true, commit = '', timeout = 15000) {
|
|
300
|
+
DB.prototype.addTable = async function (table, field, type = 'int', auto = true, commit = '', timeout = 15000) {
|
|
301
301
|
try {
|
|
302
302
|
if (!table || typeof table !== 'string') {
|
|
303
303
|
$.log.error("[DB] [addTable] 表名无效");
|
|
@@ -316,10 +316,10 @@ DB.prototype.addTable = async function(table, field, type = 'int', auto = true,
|
|
|
316
316
|
fieldDef += " COMMENT '" + commit + "'";
|
|
317
317
|
}
|
|
318
318
|
fieldDef += " PRIMARY KEY";
|
|
319
|
-
|
|
319
|
+
|
|
320
320
|
let sql = "CREATE TABLE IF NOT EXISTS \`" + table + "\` (" + fieldDef + ")";
|
|
321
321
|
sql += " ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
|
|
322
|
-
|
|
322
|
+
|
|
323
323
|
// 执行SQL并设置表名
|
|
324
324
|
const result = await this.exec(sql, timeout);
|
|
325
325
|
// 设置实例的表名属性,以便后续操作使用
|
|
@@ -337,7 +337,7 @@ DB.prototype.addTable = async function(table, field, type = 'int', auto = true,
|
|
|
337
337
|
* @param {Number} timeout 超时时间(毫秒)
|
|
338
338
|
* @return {Promise|Number} 操作结果
|
|
339
339
|
*/
|
|
340
|
-
DB.prototype.dropTable = function(table, timeout = 15000) {
|
|
340
|
+
DB.prototype.dropTable = function (table, timeout = 15000) {
|
|
341
341
|
if (!table || typeof table !== 'string') {
|
|
342
342
|
$.log.error("[DB] [dropTable] 表名无效");
|
|
343
343
|
return 0;
|
|
@@ -352,7 +352,7 @@ DB.prototype.dropTable = function(table, timeout = 15000) {
|
|
|
352
352
|
* @param {Number} timeout 超时时间(毫秒)
|
|
353
353
|
* @return {Promise|Number} 操作结果
|
|
354
354
|
*/
|
|
355
|
-
DB.prototype.renameTable = function(table, new_table, timeout = 15000) {
|
|
355
|
+
DB.prototype.renameTable = function (table, new_table, timeout = 15000) {
|
|
356
356
|
if (!table || !new_table) {
|
|
357
357
|
$.log.error("[DB] [renameTable] 表名参数不完整");
|
|
358
358
|
return 0;
|
|
@@ -371,27 +371,27 @@ DB.prototype.renameTable = function(table, new_table, timeout = 15000) {
|
|
|
371
371
|
* @param {Number} timeout 超时时间(毫秒)
|
|
372
372
|
* @return {Promise|Number} 添加成功返回1,失败返回0
|
|
373
373
|
*/
|
|
374
|
-
DB.prototype.addField = async function(field, type, value = '', not_null = true, auto = false, comment = '', timeout = 15000) {
|
|
374
|
+
DB.prototype.addField = async function (field, type, value = '', not_null = true, auto = false, comment = '', timeout = 15000) {
|
|
375
375
|
try {
|
|
376
376
|
// 确保表名已设置
|
|
377
377
|
if (!this.table || !field || !type) {
|
|
378
378
|
$.log.error("[DB] [addField] 表名、字段名或类型未指定");
|
|
379
379
|
return 0;
|
|
380
380
|
}
|
|
381
|
-
|
|
381
|
+
|
|
382
382
|
// 构建字段定义
|
|
383
383
|
let fieldDef = `\`${field}\` ${type}`;
|
|
384
|
-
|
|
384
|
+
|
|
385
385
|
// 添加非空约束
|
|
386
386
|
if (not_null) {
|
|
387
387
|
fieldDef += " NOT NULL";
|
|
388
388
|
}
|
|
389
|
-
|
|
389
|
+
|
|
390
390
|
// 添加自增属性
|
|
391
391
|
if (auto) {
|
|
392
392
|
fieldDef += " AUTO_INCREMENT";
|
|
393
393
|
}
|
|
394
|
-
|
|
394
|
+
|
|
395
395
|
// 添加默认值
|
|
396
396
|
if (value !== undefined && value !== null && value !== '') {
|
|
397
397
|
if (typeof value === 'string') {
|
|
@@ -400,12 +400,12 @@ DB.prototype.addField = async function(field, type, value = '', not_null = true,
|
|
|
400
400
|
fieldDef += " DEFAULT " + value;
|
|
401
401
|
}
|
|
402
402
|
}
|
|
403
|
-
|
|
403
|
+
|
|
404
404
|
// 添加注释
|
|
405
405
|
if (comment) {
|
|
406
406
|
fieldDef += " COMMENT '" + comment + "'";
|
|
407
407
|
}
|
|
408
|
-
|
|
408
|
+
|
|
409
409
|
// 使用ADD COLUMN而不是CHANGE COLUMN
|
|
410
410
|
const sql = `ALTER TABLE \`${this.table}\` ADD COLUMN ${fieldDef};`;
|
|
411
411
|
const result = await this.exec(sql, timeout);
|
|
@@ -428,7 +428,7 @@ DB.prototype.addField = async function(field, type, value = '', not_null = true,
|
|
|
428
428
|
* @param {Number} timeout 超时时间(毫秒)
|
|
429
429
|
* @return {Promise|Number} 修改成功返回1,失败返回0
|
|
430
430
|
*/
|
|
431
|
-
DB.prototype.setField = async function(field, type, value = '', not_null = true, auto = false, isKey = false, new_name = '', timeout = 15000) {
|
|
431
|
+
DB.prototype.setField = async function (field, type, value = '', not_null = true, auto = false, isKey = false, new_name = '', timeout = 15000) {
|
|
432
432
|
try {
|
|
433
433
|
if (!field || !type || !this.table) {
|
|
434
434
|
$.log.error("[DB] [setField] 参数不完整或表名未设置");
|
|
@@ -470,7 +470,7 @@ DB.prototype.setField = async function(field, type, value = '', not_null = true,
|
|
|
470
470
|
* @param {Number} timeout 超时时间(毫秒)
|
|
471
471
|
* @return {Promise|Number} 操作结果
|
|
472
472
|
*/
|
|
473
|
-
DB.prototype.editField = function(table, field, type, timeout = 15000) {
|
|
473
|
+
DB.prototype.editField = function (table, field, type, timeout = 15000) {
|
|
474
474
|
if (!table || !field || !type) {
|
|
475
475
|
$.log.error("[DB] [editField] 参数不完整");
|
|
476
476
|
return 0;
|
|
@@ -485,7 +485,7 @@ DB.prototype.editField = function(table, field, type, timeout = 15000) {
|
|
|
485
485
|
* @param {Number} timeout 超时时间(毫秒)
|
|
486
486
|
* @return {Promise|Number} 操作结果
|
|
487
487
|
*/
|
|
488
|
-
DB.prototype.delField = function(table, field, timeout = 15000) {
|
|
488
|
+
DB.prototype.delField = function (table, field, timeout = 15000) {
|
|
489
489
|
if (!table || !field) {
|
|
490
490
|
$.log.error("[DB] [delField] 参数不完整");
|
|
491
491
|
return 0;
|
|
@@ -502,7 +502,7 @@ DB.prototype.delField = function(table, field, timeout = 15000) {
|
|
|
502
502
|
* @param {Number} timeout 超时时间(毫秒)
|
|
503
503
|
* @return {Promise|Number} 操作结果
|
|
504
504
|
*/
|
|
505
|
-
DB.prototype.renameField = function(table, field, new_field, type, timeout = 15000) {
|
|
505
|
+
DB.prototype.renameField = function (table, field, new_field, type, timeout = 15000) {
|
|
506
506
|
if (!table || !field || !new_field) {
|
|
507
507
|
$.log.error("[DB] [renameField] 参数不完整");
|
|
508
508
|
return 0;
|
|
@@ -516,7 +516,7 @@ DB.prototype.renameField = function(table, field, new_field, type, timeout = 150
|
|
|
516
516
|
* @param {Number} timeout 超时时间(毫秒)
|
|
517
517
|
* @return {Promise|String} 创建语句
|
|
518
518
|
*/
|
|
519
|
-
DB.prototype.getCreateTable = async function(table, timeout = 15000) {
|
|
519
|
+
DB.prototype.getCreateTable = async function (table, timeout = 15000) {
|
|
520
520
|
if (!table) {
|
|
521
521
|
$.log.error("[DB] [getCreateTable] 表名不能为空");
|
|
522
522
|
return '';
|
|
@@ -540,7 +540,7 @@ DB.prototype.getCreateTable = async function(table, timeout = 15000) {
|
|
|
540
540
|
* @param {Number} timeout 超时时间(毫秒)
|
|
541
541
|
* @return {Promise|String} SQL插入语句
|
|
542
542
|
*/
|
|
543
|
-
DB.prototype.getTableData = async function(table, batchSize = 100, timeout = 60000) {
|
|
543
|
+
DB.prototype.getTableData = async function (table, batchSize = 100, timeout = 60000) {
|
|
544
544
|
if (!table) {
|
|
545
545
|
$.log.error("[DB] [getTableData] 表名不能为空");
|
|
546
546
|
return '';
|
|
@@ -549,10 +549,10 @@ DB.prototype.getTableData = async function(table, batchSize = 100, timeout = 600
|
|
|
549
549
|
// 获取表数据
|
|
550
550
|
const rows = await this.run(`SELECT * FROM \`${table}\``, [], timeout);
|
|
551
551
|
let sql = '';
|
|
552
|
-
|
|
552
|
+
|
|
553
553
|
if (rows && rows.length > 0) {
|
|
554
554
|
sql += `-- 表数据: ${table}\n`;
|
|
555
|
-
|
|
555
|
+
|
|
556
556
|
// 批量处理大数据量
|
|
557
557
|
for (let j = 0; j < rows.length; j += batchSize) {
|
|
558
558
|
const batch = rows.slice(j, j + batchSize);
|
|
@@ -598,7 +598,7 @@ DB.prototype.getTableData = async function(table, batchSize = 100, timeout = 600
|
|
|
598
598
|
* @param {Number} timeout 超时时间(毫秒)
|
|
599
599
|
* @return {Promise|Number} 操作结果
|
|
600
600
|
*/
|
|
601
|
-
DB.prototype.emptyTable = function(table, timeout = 15000) {
|
|
601
|
+
DB.prototype.emptyTable = function (table, timeout = 15000) {
|
|
602
602
|
if (!table || typeof table !== 'string') {
|
|
603
603
|
$.log.error("[DB] [emptyTable] 表名无效");
|
|
604
604
|
return 0;
|
|
@@ -612,7 +612,7 @@ DB.prototype.emptyTable = function(table, timeout = 15000) {
|
|
|
612
612
|
* @param {Number} timeout 超时时间(毫秒)
|
|
613
613
|
* @return {Promise|Boolean} 是否存在
|
|
614
614
|
*/
|
|
615
|
-
DB.prototype.hasTable = async function(table, timeout = 15000) {
|
|
615
|
+
DB.prototype.hasTable = async function (table, timeout = 15000) {
|
|
616
616
|
if (!table || typeof table !== 'string') {
|
|
617
617
|
return false;
|
|
618
618
|
}
|
|
@@ -632,7 +632,7 @@ DB.prototype.hasTable = async function(table, timeout = 15000) {
|
|
|
632
632
|
* @param {Number} timeout 超时时间(毫秒)
|
|
633
633
|
* @return {Promise|Boolean} 是否存在
|
|
634
634
|
*/
|
|
635
|
-
DB.prototype.hasField = async function(table, field, timeout = 15000) {
|
|
635
|
+
DB.prototype.hasField = async function (table, field, timeout = 15000) {
|
|
636
636
|
if (!table || !field) {
|
|
637
637
|
return false;
|
|
638
638
|
}
|
|
@@ -652,7 +652,7 @@ DB.prototype.hasField = async function(table, field, timeout = 15000) {
|
|
|
652
652
|
* @param {Number} timeout 超时时间(毫秒)
|
|
653
653
|
* @return {Promise|Number} 操作结果
|
|
654
654
|
*/
|
|
655
|
-
DB.prototype.backupTable = function(table, backup, timeout = 60000) {
|
|
655
|
+
DB.prototype.backupTable = function (table, backup, timeout = 60000) {
|
|
656
656
|
if (!table || !backup) {
|
|
657
657
|
$.log.error("[DB] [backupTable] 参数不完整");
|
|
658
658
|
return 0;
|
|
@@ -660,6 +660,87 @@ DB.prototype.backupTable = function(table, backup, timeout = 60000) {
|
|
|
660
660
|
return this.exec("CREATE TABLE \`" + backup + "\` LIKE \`" + table + "\`; INSERT INTO \`" + backup + "\` SELECT * FROM \`" + table + "\`;", timeout);
|
|
661
661
|
};
|
|
662
662
|
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* @description 创建表
|
|
666
|
+
* @param {String} table 表名
|
|
667
|
+
* @param {Object} model 表模型,键值对,根据值类型创建字段类型,根据键名创建字段名
|
|
668
|
+
* @param {String} key 主键
|
|
669
|
+
* @return {Promise|Number} 操作结果
|
|
670
|
+
*/
|
|
671
|
+
DB.prototype.createTable = function (table, model, key = 'id', timeout = 15000) {
|
|
672
|
+
if (!table || !model || !model.fields) {
|
|
673
|
+
$.log.error("[DB] [createTable] 参数不完整");
|
|
674
|
+
return 0;
|
|
675
|
+
}
|
|
676
|
+
var fields = '';
|
|
677
|
+
for (const field in model.fields) {
|
|
678
|
+
const value = model.fields[field];
|
|
679
|
+
let type = '';
|
|
680
|
+
if (field === key) {
|
|
681
|
+
type = 'INT AUTO_INCREMENT PRIMARY KEY';
|
|
682
|
+
} else if (typeof value === 'number') {
|
|
683
|
+
if (value % 1 === 0) {
|
|
684
|
+
// 判断是否时间戳(10位或13位数字)
|
|
685
|
+
const now = Date.now();
|
|
686
|
+
const isTimestamp = (value >= 1000000000 && value <= now * 1.1); // 10位或13位时间戳
|
|
687
|
+
if (isTimestamp) {
|
|
688
|
+
type = 'DATETIME';
|
|
689
|
+
} else {
|
|
690
|
+
type = 'INT';
|
|
691
|
+
}
|
|
692
|
+
} else {
|
|
693
|
+
type = 'FLOAT';
|
|
694
|
+
}
|
|
695
|
+
} else if (typeof value === 'string') {
|
|
696
|
+
type = 'TEXT';
|
|
697
|
+
if (value.length <= 255) {
|
|
698
|
+
// 正则判断是否日期时间型
|
|
699
|
+
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(value)) {
|
|
700
|
+
type = 'DATETIME';
|
|
701
|
+
}
|
|
702
|
+
// 正则判断是否日期型
|
|
703
|
+
else if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
704
|
+
type = 'DATE';
|
|
705
|
+
}
|
|
706
|
+
// 正则判断是否时间型
|
|
707
|
+
else if (/^\d{2}:\d{2}:\d{2}$/.test(value)) {
|
|
708
|
+
type = 'TIME';
|
|
709
|
+
}
|
|
710
|
+
// 正则判断是否JSON字符串
|
|
711
|
+
else if (/^[{[]/.test(value) && /[}]]$/.test(value)) {
|
|
712
|
+
type = 'TEXT';
|
|
713
|
+
}
|
|
714
|
+
// 正则判断是否html
|
|
715
|
+
else if (/^<[a-z][\s\S]*>/i.test(value)) {
|
|
716
|
+
type = 'TEXT';
|
|
717
|
+
}
|
|
718
|
+
// 正则判断是否xml
|
|
719
|
+
else if (/^<\?xml[\s\S]*\?>/.test(value)) {
|
|
720
|
+
type = 'TEXT';
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
type = 'VARCHAR(255)';
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
else if (typeof value === 'boolean') {
|
|
728
|
+
type = 'tinyint(1)';
|
|
729
|
+
}
|
|
730
|
+
else if (value instanceof Date) {
|
|
731
|
+
// 判断是否Date对象,类型为datetime
|
|
732
|
+
type = 'DATETIME';
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
type = 'BLOB';
|
|
736
|
+
}
|
|
737
|
+
fields += `\`${field}\` ${type}, `;
|
|
738
|
+
}
|
|
739
|
+
fields = fields.slice(0, -2);
|
|
740
|
+
var sql = `CREATE TABLE IF NOT EXISTS \`${table}\` (${fields});`;
|
|
741
|
+
return this.exec(sql, [], timeout);
|
|
742
|
+
}
|
|
743
|
+
|
|
663
744
|
module.exports = {
|
|
664
745
|
DB
|
|
665
746
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const { Mysql } = require('./index');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 测试 createTable 方法功能
|
|
5
|
+
*/
|
|
6
|
+
async function testCreateTable() {
|
|
7
|
+
console.log('开始测试 createTable 方法...');
|
|
8
|
+
|
|
9
|
+
let mysql = null;
|
|
10
|
+
const testDatabase = 'test_mm_mysql';
|
|
11
|
+
const testTable = 'test_users';
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
// 初始化 MySQL 连接
|
|
15
|
+
mysql = new Mysql({
|
|
16
|
+
host: '127.0.0.1',
|
|
17
|
+
port: 3306,
|
|
18
|
+
user: 'root',
|
|
19
|
+
password: 'Asd159357',
|
|
20
|
+
database: testDatabase,
|
|
21
|
+
connectionLimit: 1
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// 打开连接
|
|
25
|
+
await mysql.open();
|
|
26
|
+
console.log('✅ 数据库连接成功');
|
|
27
|
+
|
|
28
|
+
// 获取 DB 实例
|
|
29
|
+
const db = mysql.db();
|
|
30
|
+
|
|
31
|
+
// 定义测试表模型
|
|
32
|
+
const userModel = {
|
|
33
|
+
fields: {
|
|
34
|
+
id: 0, // 主键,会被自动设置为 INT AUTO_INCREMENT PRIMARY KEY
|
|
35
|
+
username: 'admin', // 字符串,长度 <= 255,会被设置为 VARCHAR(255)
|
|
36
|
+
email: 'test@example.com', // 字符串,会被设置为 VARCHAR(255)
|
|
37
|
+
age: 25, // 整数,会被设置为 INT
|
|
38
|
+
salary: 5000.50, // 浮点数,会被设置为 FLOAT
|
|
39
|
+
active: true, // boolean 类型,会被设置为 INT
|
|
40
|
+
birth_date: '1990-01-01', // date 类型,会被设置为 DATE
|
|
41
|
+
bio: 'This is a very long bio about the user that exceeds 255 characters in length. ' +
|
|
42
|
+
'This is necessary to test the text type functionality in the createTable method. ' +
|
|
43
|
+
'By making this string longer than 255 characters, we can ensure that the field ' +
|
|
44
|
+
'is created as a TEXT type instead of a VARCHAR(255).', // 字符串,长度 > 255,会被设置为 TEXT
|
|
45
|
+
created_at: '2023-10-25 10:30:00', // 日期时间字符串,会被设置为 DATETIME
|
|
46
|
+
login_time: new Date(), // Date 对象,会被设置为 DATETIME
|
|
47
|
+
last_active: Date.now() // 时间戳,会被设置为 DATETIME
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// 创建表
|
|
52
|
+
console.log('开始创建测试表...');
|
|
53
|
+
const createResult = await db.createTable(testTable, userModel, 'id', 15000);
|
|
54
|
+
|
|
55
|
+
if (createResult && createResult.affectedRows !== undefined) {
|
|
56
|
+
console.log('✅ 测试表创建成功');
|
|
57
|
+
console.log(' 影响行数:', createResult.affectedRows);
|
|
58
|
+
} else {
|
|
59
|
+
console.log('⚠️ 表可能已存在(CREATE TABLE IF NOT EXISTS)');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 验证表是否存在
|
|
63
|
+
console.log('\n验证表结构...');
|
|
64
|
+
const tableExistsSql = `SHOW TABLES LIKE '${testTable}'`;
|
|
65
|
+
const tables = await mysql.run(tableExistsSql);
|
|
66
|
+
|
|
67
|
+
if (tables.length > 0) {
|
|
68
|
+
console.log('✅ 测试表确实存在');
|
|
69
|
+
} else {
|
|
70
|
+
throw new Error('测试表不存在');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 查看表结构
|
|
74
|
+
const describeSql = `DESCRIBE ${testTable}`;
|
|
75
|
+
const tableStructure = await mysql.run(describeSql);
|
|
76
|
+
|
|
77
|
+
console.log('\n表结构:');
|
|
78
|
+
tableStructure.forEach(row => {
|
|
79
|
+
console.log(` ${row.Field}: ${row.Type} ${row.Null} ${row.Key} ${row.Default} ${row.Extra}`);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 验证字段类型是否正确
|
|
83
|
+
const fieldTypes = {
|
|
84
|
+
id: 'int',
|
|
85
|
+
username: 'varchar(255)',
|
|
86
|
+
email: 'varchar(255)',
|
|
87
|
+
age: 'int',
|
|
88
|
+
salary: 'float',
|
|
89
|
+
active: 'int', // MySQL 中 boolean 会被转换为 tinyint 或 int
|
|
90
|
+
birth_date: 'date',
|
|
91
|
+
bio: 'text',
|
|
92
|
+
created_at: 'datetime',
|
|
93
|
+
login_time: 'datetime', // Date 对象会被设置为 DATETIME
|
|
94
|
+
last_active: 'datetime' // 时间戳会被设置为 DATETIME
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
let allFieldsCorrect = true;
|
|
98
|
+
tableStructure.forEach(row => {
|
|
99
|
+
const expectedType = fieldTypes[row.Field];
|
|
100
|
+
if (expectedType && !row.Type.toLowerCase().includes(expectedType)) {
|
|
101
|
+
console.log(`❌ 字段 ${row.Field} 类型不正确,预期: ${expectedType},实际: ${row.Type}`);
|
|
102
|
+
allFieldsCorrect = false;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (allFieldsCorrect) {
|
|
107
|
+
console.log('✅ 所有字段类型符合预期');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 测试插入数据
|
|
111
|
+
console.log('\n测试插入数据...');
|
|
112
|
+
const currentTime = new Date();
|
|
113
|
+
const insertSql = `INSERT INTO ${testTable} (username, email, age, salary, active, birth_date, bio, created_at, login_time, last_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
|
114
|
+
const insertResult = await mysql.exec(insertSql, ['testuser', 'testuser@example.com', 30, 6000.75, true, '1995-05-15', 'Test user bio', '2023-10-25 11:00:00', currentTime, currentTime]);
|
|
115
|
+
|
|
116
|
+
if (insertResult.insertId) {
|
|
117
|
+
console.log('✅ 数据插入成功,插入ID:', insertResult.insertId);
|
|
118
|
+
} else {
|
|
119
|
+
throw new Error('数据插入失败');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 测试查询数据
|
|
123
|
+
console.log('\n测试查询数据...');
|
|
124
|
+
const selectSql = `SELECT * FROM ${testTable} WHERE id = ?`;
|
|
125
|
+
const user = await mysql.run(selectSql, [insertResult.insertId]);
|
|
126
|
+
|
|
127
|
+
if (user && user.length > 0) {
|
|
128
|
+
console.log('✅ 数据查询成功');
|
|
129
|
+
console.log(' 查询结果:', user[0]);
|
|
130
|
+
} else {
|
|
131
|
+
throw new Error('数据查询失败');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 测试使用自定义主键
|
|
135
|
+
console.log('\n测试使用自定义主键...');
|
|
136
|
+
const customKeyTable = 'test_custom_key';
|
|
137
|
+
const customModel = {
|
|
138
|
+
fields: {
|
|
139
|
+
user_id: 'u001', // 自定义主键,会被设置为 INT AUTO_INCREMENT PRIMARY KEY
|
|
140
|
+
name: 'test', // 字符串
|
|
141
|
+
value: 100, // 整数
|
|
142
|
+
is_active: true, // boolean 类型
|
|
143
|
+
join_date: '2023-01-01' // date 类型
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const customCreateResult = await db.createTable(customKeyTable, customModel, 'user_id', 15000);
|
|
148
|
+
if (customCreateResult || customCreateResult === 0) {
|
|
149
|
+
console.log('✅ 使用自定义主键创建表成功');
|
|
150
|
+
|
|
151
|
+
// 验证自定义主键表结构
|
|
152
|
+
const customDescribeSql = `DESCRIBE ${customKeyTable}`;
|
|
153
|
+
const customStructure = await mysql.run(customDescribeSql);
|
|
154
|
+
const customKeyField = customStructure.find(field => field.Field === 'user_id');
|
|
155
|
+
|
|
156
|
+
if (customKeyField && customKeyField.Key === 'PRI') {
|
|
157
|
+
console.log('✅ 自定义主键设置正确');
|
|
158
|
+
} else {
|
|
159
|
+
console.log('⚠️ 自定义主键可能未设置为 PRIMARY KEY');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 清理测试表
|
|
164
|
+
console.log('\n清理测试环境...');
|
|
165
|
+
await mysql.exec(`DROP TABLE IF EXISTS ${testTable}`);
|
|
166
|
+
await mysql.exec(`DROP TABLE IF EXISTS ${customKeyTable}`);
|
|
167
|
+
console.log('✅ 测试表已删除');
|
|
168
|
+
|
|
169
|
+
console.log('\n🎉 所有测试通过!createTable 方法功能正常');
|
|
170
|
+
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error('❌ 测试失败:', error.message);
|
|
173
|
+
console.error('错误详情:', error);
|
|
174
|
+
return false;
|
|
175
|
+
} finally {
|
|
176
|
+
// 关闭连接
|
|
177
|
+
if (mysql) {
|
|
178
|
+
try {
|
|
179
|
+
await mysql.close();
|
|
180
|
+
console.log('✅ 数据库连接已关闭');
|
|
181
|
+
} catch (closeError) {
|
|
182
|
+
console.error('⚠️ 关闭数据库连接失败:', closeError.message);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 运行测试
|
|
191
|
+
testCreateTable().then(success => {
|
|
192
|
+
process.exit(success ? 0 : 1);
|
|
193
|
+
});
|