@koishijs/plugin-database-mysql 4.2.0 → 4.3.2
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/lib/index.d.ts +6 -86
- package/lib/index.js +24 -442
- package/lib/index.js.map +2 -2
- package/package.json +6 -12
package/lib/index.d.ts
CHANGED
|
@@ -1,86 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
declare
|
|
7
|
-
interface UntypedFieldInfo {
|
|
8
|
-
packet: UntypedFieldInfo;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
declare module 'koishi' {
|
|
12
|
-
interface Database {
|
|
13
|
-
mysql: MysqlDatabase;
|
|
14
|
-
}
|
|
15
|
-
interface Modules {
|
|
16
|
-
'database-mysql': typeof import('.');
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
declare class MySQLBuilder extends Builder {
|
|
20
|
-
private model;
|
|
21
|
-
constructor(model: Model);
|
|
22
|
-
format(sql: string, values: any[], stringifyObjects?: boolean, timeZone?: string): string;
|
|
23
|
-
escapeId(value: string, forbidQualified?: boolean): string;
|
|
24
|
-
escape(value: any, table?: string, field?: string): string;
|
|
25
|
-
stringify(value: any, table?: string, field?: string): any;
|
|
26
|
-
}
|
|
27
|
-
declare class MysqlDatabase extends Database {
|
|
28
|
-
ctx: Context;
|
|
29
|
-
pool: Pool;
|
|
30
|
-
config: MysqlDatabase.Config;
|
|
31
|
-
mysql: this;
|
|
32
|
-
sql: MySQLBuilder;
|
|
33
|
-
private _tableTasks;
|
|
34
|
-
private _queryTasks;
|
|
35
|
-
constructor(ctx: Context, config?: MysqlDatabase.Config);
|
|
36
|
-
start(): Promise<void>;
|
|
37
|
-
stop(): void;
|
|
38
|
-
private _getColDefs;
|
|
39
|
-
/** synchronize table schema */
|
|
40
|
-
private _syncTable;
|
|
41
|
-
_inferFields<T extends keyof Tables>(table: T, keys: readonly string[]): (keyof Tables[T])[];
|
|
42
|
-
_createFilter(name: keyof Tables, query: Query): string;
|
|
43
|
-
_joinKeys: (keys: readonly string[]) => string;
|
|
44
|
-
_formatValues: (table: string, data: object, keys: readonly string[]) => any[];
|
|
45
|
-
query<T = any>(sql: string, values?: any): Promise<T>;
|
|
46
|
-
queue<T = any>(sql: string, values?: any): Promise<T>;
|
|
47
|
-
private _flushTasks;
|
|
48
|
-
select<T extends {}>(table: string, fields: readonly (string & keyof T)[], conditional?: string, values?: readonly any[]): Promise<T[]>;
|
|
49
|
-
drop(): Promise<void>;
|
|
50
|
-
stats(): Promise<Driver.Stats>;
|
|
51
|
-
get(name: keyof Tables, query: Query, modifier?: Modifier): Promise<any>;
|
|
52
|
-
private toUpdateExpr;
|
|
53
|
-
set(name: keyof Tables, query: Query, data: {}): Promise<void>;
|
|
54
|
-
remove(name: keyof Tables, query: Query): Promise<void>;
|
|
55
|
-
create<T extends keyof Tables>(name: T, data: {}): Promise<any>;
|
|
56
|
-
upsert(name: keyof Tables, data: any[], keys: string | string[]): Promise<void>;
|
|
57
|
-
eval(name: keyof Tables, expr: any, query: Query): Promise<any>;
|
|
58
|
-
}
|
|
59
|
-
declare namespace MysqlDatabase {
|
|
60
|
-
export interface Config extends PoolConfig {
|
|
61
|
-
}
|
|
62
|
-
export const Config: Schema<{
|
|
63
|
-
host?: string;
|
|
64
|
-
port?: number;
|
|
65
|
-
user?: string;
|
|
66
|
-
password?: string;
|
|
67
|
-
database?: string;
|
|
68
|
-
} & import("schemastery").Dict<any, string>, {
|
|
69
|
-
host?: string;
|
|
70
|
-
port?: number;
|
|
71
|
-
user?: string;
|
|
72
|
-
password?: string;
|
|
73
|
-
database?: string;
|
|
74
|
-
} & import("schemastery").Dict<any, string>>;
|
|
75
|
-
type Declarations = {
|
|
76
|
-
[T in keyof Tables]?: {
|
|
77
|
-
[K in keyof Tables[T]]?: () => string;
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
/**
|
|
81
|
-
* @deprecated use `import('koishi').Field` instead
|
|
82
|
-
*/
|
|
83
|
-
export const tables: Declarations;
|
|
84
|
-
export {};
|
|
85
|
-
}
|
|
86
|
-
export default MysqlDatabase;
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
import MySQLDriver from '@cosmotype/driver-mysql';
|
|
3
|
+
export declare const name = "MemoryDatabase";
|
|
4
|
+
export declare type Config = MySQLDriver.Config;
|
|
5
|
+
export declare const Config: Schema<Config>;
|
|
6
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -1,32 +1,15 @@
|
|
|
1
1
|
var __create = Object.create;
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
-
var __defProps = Object.defineProperties;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
6
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
8
5
|
var __getProtoOf = Object.getPrototypeOf;
|
|
9
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
-
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
11
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
12
|
-
var __spreadValues = (a, b) => {
|
|
13
|
-
for (var prop in b || (b = {}))
|
|
14
|
-
if (__hasOwnProp.call(b, prop))
|
|
15
|
-
__defNormalProp(a, prop, b[prop]);
|
|
16
|
-
if (__getOwnPropSymbols)
|
|
17
|
-
for (var prop of __getOwnPropSymbols(b)) {
|
|
18
|
-
if (__propIsEnum.call(b, prop))
|
|
19
|
-
__defNormalProp(a, prop, b[prop]);
|
|
20
|
-
}
|
|
21
|
-
return a;
|
|
22
|
-
};
|
|
23
|
-
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
24
7
|
var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
|
|
25
8
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
26
9
|
var __export = (target, all) => {
|
|
27
10
|
__markAsModule(target);
|
|
28
|
-
for (var
|
|
29
|
-
__defProp(target,
|
|
11
|
+
for (var name2 in all)
|
|
12
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
30
13
|
};
|
|
31
14
|
var __reExport = (target, module2, desc) => {
|
|
32
15
|
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
|
|
@@ -42,431 +25,30 @@ var __toModule = (module2) => {
|
|
|
42
25
|
|
|
43
26
|
// plugins/database/mysql/src/index.ts
|
|
44
27
|
__export(exports, {
|
|
45
|
-
|
|
28
|
+
Config: () => Config,
|
|
29
|
+
apply: () => apply,
|
|
30
|
+
name: () => name
|
|
46
31
|
});
|
|
47
|
-
var import_mysql = __toModule(require("@vlasky/mysql"));
|
|
48
32
|
var import_koishi = __toModule(require("koishi"));
|
|
49
|
-
var
|
|
50
|
-
var
|
|
51
|
-
var
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return "bigint";
|
|
63
|
-
}
|
|
64
|
-
__name(getIntegerType, "getIntegerType");
|
|
65
|
-
function getTypeDefinition({ type, length, precision, scale }) {
|
|
66
|
-
switch (type) {
|
|
67
|
-
case "float":
|
|
68
|
-
case "double":
|
|
69
|
-
case "date":
|
|
70
|
-
case "time":
|
|
71
|
-
return type;
|
|
72
|
-
case "timestamp":
|
|
73
|
-
return "datetime";
|
|
74
|
-
case "integer":
|
|
75
|
-
return getIntegerType(length);
|
|
76
|
-
case "unsigned":
|
|
77
|
-
return `${getIntegerType(length)} unsigned`;
|
|
78
|
-
case "decimal":
|
|
79
|
-
return `decimal(${precision}, ${scale}) unsigned`;
|
|
80
|
-
case "char":
|
|
81
|
-
return `char(${length || 255})`;
|
|
82
|
-
case "string":
|
|
83
|
-
return `varchar(${length || 255})`;
|
|
84
|
-
case "text":
|
|
85
|
-
return `text(${length || 65535})`;
|
|
86
|
-
case "list":
|
|
87
|
-
return `text(${length || 65535})`;
|
|
88
|
-
case "json":
|
|
89
|
-
return `text(${length || 65535})`;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
__name(getTypeDefinition, "getTypeDefinition");
|
|
93
|
-
function backtick(str) {
|
|
94
|
-
return "`" + str + "`";
|
|
95
|
-
}
|
|
96
|
-
__name(backtick, "backtick");
|
|
97
|
-
function createIndex(keys) {
|
|
98
|
-
return (0, import_koishi.makeArray)(keys).map(backtick).join(", ");
|
|
33
|
+
var import_driver_mysql = __toModule(require("@cosmotype/driver-mysql"));
|
|
34
|
+
var name = "MemoryDatabase";
|
|
35
|
+
var Config = import_koishi.Schema.object({
|
|
36
|
+
host: import_koishi.Schema.string().description("要连接到的主机名。").default("localhost"),
|
|
37
|
+
port: import_koishi.Schema.natural().max(65535).description("要连接到的端口号。").default(3306),
|
|
38
|
+
user: import_koishi.Schema.string().description("要使用的用户名。").default("root"),
|
|
39
|
+
password: import_koishi.Schema.string().description("要使用的密码。").role("secret"),
|
|
40
|
+
database: import_koishi.Schema.string().description("要访问的数据库名。").default("koishi")
|
|
41
|
+
});
|
|
42
|
+
function apply(ctx, config) {
|
|
43
|
+
const driver = new import_driver_mysql.default(ctx.model, config);
|
|
44
|
+
ctx.on("ready", () => driver.start());
|
|
45
|
+
ctx.on("dispose", () => driver.stop());
|
|
99
46
|
}
|
|
100
|
-
__name(
|
|
101
|
-
var MySQLBuilder = class extends import_sql_utils.Builder {
|
|
102
|
-
constructor(model) {
|
|
103
|
-
super();
|
|
104
|
-
this.model = model;
|
|
105
|
-
}
|
|
106
|
-
format(sql, values, stringifyObjects, timeZone) {
|
|
107
|
-
return (0, import_mysql.format)(sql, values, stringifyObjects, timeZone);
|
|
108
|
-
}
|
|
109
|
-
escapeId(value, forbidQualified) {
|
|
110
|
-
return (0, import_mysql.escapeId)(value, forbidQualified);
|
|
111
|
-
}
|
|
112
|
-
escape(value, table, field) {
|
|
113
|
-
return (0, import_mysql.escape)(this.stringify(value, table, field));
|
|
114
|
-
}
|
|
115
|
-
stringify(value, table, field) {
|
|
116
|
-
var _a, _b;
|
|
117
|
-
const type = (_a = MysqlDatabase.tables[table]) == null ? void 0 : _a[field];
|
|
118
|
-
if (typeof type === "object")
|
|
119
|
-
return type.stringify(value);
|
|
120
|
-
const meta = (_b = this.model.config[table]) == null ? void 0 : _b.fields[field];
|
|
121
|
-
if ((meta == null ? void 0 : meta.type) === "json") {
|
|
122
|
-
return JSON.stringify(value);
|
|
123
|
-
} else if ((meta == null ? void 0 : meta.type) === "list") {
|
|
124
|
-
return value.join(",");
|
|
125
|
-
} else if (import_orm.Model.Field.date.includes(meta == null ? void 0 : meta.type)) {
|
|
126
|
-
return import_koishi.Time.template("yyyy-MM-dd hh:mm:ss", value);
|
|
127
|
-
}
|
|
128
|
-
return value;
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
__name(MySQLBuilder, "MySQLBuilder");
|
|
132
|
-
var MysqlDatabase = class extends import_koishi.Database {
|
|
133
|
-
constructor(ctx, config) {
|
|
134
|
-
super(ctx);
|
|
135
|
-
this.ctx = ctx;
|
|
136
|
-
this.mysql = this;
|
|
137
|
-
this._tableTasks = {};
|
|
138
|
-
this._queryTasks = [];
|
|
139
|
-
this._joinKeys = (keys) => {
|
|
140
|
-
return keys ? keys.map((key) => key.includes("`") ? key : `\`${key}\``).join(",") : "*";
|
|
141
|
-
};
|
|
142
|
-
this._formatValues = (table, data, keys) => {
|
|
143
|
-
return keys.map((key) => this.sql.stringify(data[key], table, key));
|
|
144
|
-
};
|
|
145
|
-
this.config = __spreadValues({
|
|
146
|
-
host: "localhost",
|
|
147
|
-
port: 3306,
|
|
148
|
-
user: "root",
|
|
149
|
-
database: "koishi",
|
|
150
|
-
charset: "utf8mb4_general_ci",
|
|
151
|
-
multipleStatements: true,
|
|
152
|
-
typeCast: (field, next) => {
|
|
153
|
-
var _a, _b, _c;
|
|
154
|
-
const { orgName, orgTable } = field.packet;
|
|
155
|
-
const type = (_a = MysqlDatabase.tables[orgTable]) == null ? void 0 : _a[orgName];
|
|
156
|
-
if (typeof type === "object")
|
|
157
|
-
return type.parse(field);
|
|
158
|
-
const meta = (_b = this.model.config[orgTable]) == null ? void 0 : _b.fields[orgName];
|
|
159
|
-
if (import_orm.Model.Field.string.includes(meta == null ? void 0 : meta.type)) {
|
|
160
|
-
return field.string();
|
|
161
|
-
} else if ((meta == null ? void 0 : meta.type) === "json") {
|
|
162
|
-
const source = field.string();
|
|
163
|
-
return source ? JSON.parse(source) : meta.initial;
|
|
164
|
-
} else if ((meta == null ? void 0 : meta.type) === "list") {
|
|
165
|
-
const source = field.string();
|
|
166
|
-
return source ? source.split(",") : [];
|
|
167
|
-
} else if ((meta == null ? void 0 : meta.type) === "time") {
|
|
168
|
-
const source = field.string();
|
|
169
|
-
if (!source)
|
|
170
|
-
return meta.initial;
|
|
171
|
-
const time = new Date(DEFAULT_DATE);
|
|
172
|
-
const [h, m, s] = source.split(":");
|
|
173
|
-
time.setHours(parseInt(h));
|
|
174
|
-
time.setMinutes(parseInt(m));
|
|
175
|
-
time.setSeconds(parseInt(s));
|
|
176
|
-
return time;
|
|
177
|
-
}
|
|
178
|
-
if (field.type === "BIT") {
|
|
179
|
-
return Boolean((_c = field.buffer()) == null ? void 0 : _c.readUInt8(0));
|
|
180
|
-
} else {
|
|
181
|
-
return next();
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}, config);
|
|
185
|
-
this.sql = new MySQLBuilder(this.model);
|
|
186
|
-
}
|
|
187
|
-
async start() {
|
|
188
|
-
this.pool = (0, import_mysql.createPool)(this.config);
|
|
189
|
-
for (const name in this.model.config) {
|
|
190
|
-
this._tableTasks[name] = this._syncTable(name);
|
|
191
|
-
}
|
|
192
|
-
this.ctx.on("model", (name) => {
|
|
193
|
-
this._tableTasks[name] = this._syncTable(name);
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
stop() {
|
|
197
|
-
this.pool.end();
|
|
198
|
-
}
|
|
199
|
-
_getColDefs(name, columns) {
|
|
200
|
-
const table = this.resolveTable(name);
|
|
201
|
-
const { primary, foreign, autoInc } = table;
|
|
202
|
-
const fields = __spreadValues({}, table.fields);
|
|
203
|
-
const unique = [...table.unique];
|
|
204
|
-
const result = [];
|
|
205
|
-
for (const key in fields) {
|
|
206
|
-
if (columns.includes(key))
|
|
207
|
-
continue;
|
|
208
|
-
const { initial, nullable = true } = fields[key];
|
|
209
|
-
let def = backtick(key);
|
|
210
|
-
if (key === primary && autoInc) {
|
|
211
|
-
def += " int unsigned not null auto_increment";
|
|
212
|
-
} else {
|
|
213
|
-
const typedef = getTypeDefinition(fields[key]);
|
|
214
|
-
def += " " + typedef;
|
|
215
|
-
if ((0, import_koishi.makeArray)(primary).includes(key)) {
|
|
216
|
-
def += " not null";
|
|
217
|
-
} else {
|
|
218
|
-
def += (nullable ? " " : " not ") + "null";
|
|
219
|
-
}
|
|
220
|
-
if (initial && !typedef.startsWith("text")) {
|
|
221
|
-
def += " default " + this.sql.escape(initial, name, key);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
result.push(def);
|
|
225
|
-
}
|
|
226
|
-
if (!columns.length) {
|
|
227
|
-
result.push(`primary key (${createIndex(primary)})`);
|
|
228
|
-
for (const key of unique) {
|
|
229
|
-
result.push(`unique index (${createIndex(key)})`);
|
|
230
|
-
}
|
|
231
|
-
for (const key in foreign) {
|
|
232
|
-
const [table2, key2] = foreign[key];
|
|
233
|
-
result.push(`foreign key (${backtick(key)}) references ${(0, import_mysql.escapeId)(table2)} (${backtick(key2)})`);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return result;
|
|
237
|
-
}
|
|
238
|
-
async _syncTable(name) {
|
|
239
|
-
await this._tableTasks[name];
|
|
240
|
-
const data = await this.queue("SELECT COLUMN_NAME from information_schema.columns WHERE TABLE_SCHEMA = ? && TABLE_NAME = ?", [this.config.database, name]);
|
|
241
|
-
const columns = data.map((row) => row.COLUMN_NAME);
|
|
242
|
-
const result = this._getColDefs(name, columns);
|
|
243
|
-
if (!columns.length) {
|
|
244
|
-
logger.info("auto creating table %c", name);
|
|
245
|
-
await this.queue(`CREATE TABLE ?? (${result.join(",")}) COLLATE = ?`, [name, this.config.charset]);
|
|
246
|
-
} else if (result.length) {
|
|
247
|
-
logger.info("auto updating table %c", name);
|
|
248
|
-
await this.queue(`ALTER TABLE ?? ${result.map((def) => "ADD " + def).join(",")}`, [name]);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
_inferFields(table, keys) {
|
|
252
|
-
if (!keys)
|
|
253
|
-
return;
|
|
254
|
-
const types = MysqlDatabase.tables[table] || {};
|
|
255
|
-
return keys.map((key) => {
|
|
256
|
-
const type = types[key];
|
|
257
|
-
return typeof type === "function" ? `${type()} AS ${key}` : key;
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
_createFilter(name, query) {
|
|
261
|
-
return this.sql.parseQuery(this.resolveQuery(name, query));
|
|
262
|
-
}
|
|
263
|
-
query(sql, values) {
|
|
264
|
-
const error = new Error();
|
|
265
|
-
return new Promise((resolve, reject) => {
|
|
266
|
-
sql = (0, import_mysql.format)(sql, values);
|
|
267
|
-
logger.debug("[sql]", sql);
|
|
268
|
-
this.pool.query(sql, (err, results) => {
|
|
269
|
-
if (!err)
|
|
270
|
-
return resolve(results);
|
|
271
|
-
logger.warn(sql);
|
|
272
|
-
if (err["code"] === "ER_DUP_ENTRY") {
|
|
273
|
-
err = new import_koishi.DriverError("duplicate-entry", err.message);
|
|
274
|
-
}
|
|
275
|
-
err.stack = err.message + error.stack.slice(5);
|
|
276
|
-
reject(err);
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
queue(sql, values) {
|
|
281
|
-
if (!this.config.multipleStatements) {
|
|
282
|
-
return this.query(sql, values);
|
|
283
|
-
}
|
|
284
|
-
sql = (0, import_mysql.format)(sql, values);
|
|
285
|
-
return new Promise((resolve, reject) => {
|
|
286
|
-
this._queryTasks.push({ sql, resolve, reject });
|
|
287
|
-
process.nextTick(() => this._flushTasks());
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
async _flushTasks() {
|
|
291
|
-
const tasks = this._queryTasks;
|
|
292
|
-
if (!tasks.length)
|
|
293
|
-
return;
|
|
294
|
-
this._queryTasks = [];
|
|
295
|
-
try {
|
|
296
|
-
let results = await this.query(tasks.map((task) => task.sql).join("; "));
|
|
297
|
-
if (tasks.length === 1)
|
|
298
|
-
results = [results];
|
|
299
|
-
tasks.forEach((task, index) => {
|
|
300
|
-
task.resolve(results[index]);
|
|
301
|
-
});
|
|
302
|
-
} catch (error) {
|
|
303
|
-
tasks.forEach((task) => task.reject(error));
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
select(table, fields, conditional, values = []) {
|
|
307
|
-
logger.debug(`[select] ${table}: ${fields ? fields.join(", ") : "*"}`);
|
|
308
|
-
const sql = "SELECT " + this._joinKeys(fields) + (table.includes(".") ? `FROM ${table}` : " FROM `" + table + `\` _${table}`) + (conditional ? " WHERE " + conditional : "");
|
|
309
|
-
return this.queue(sql, values);
|
|
310
|
-
}
|
|
311
|
-
async drop() {
|
|
312
|
-
const data = await this.select("information_schema.tables", ["TABLE_NAME"], "TABLE_SCHEMA = ?", [this.config.database]);
|
|
313
|
-
if (!data.length)
|
|
314
|
-
return;
|
|
315
|
-
await this.query(data.map(({ TABLE_NAME }) => `DROP TABLE ${this.sql.escapeId(TABLE_NAME)}`).join("; "));
|
|
316
|
-
}
|
|
317
|
-
async stats() {
|
|
318
|
-
const data = await this.select("information_schema.tables", ["TABLE_NAME", "TABLE_ROWS", "DATA_LENGTH"], "TABLE_SCHEMA = ?", [this.config.database]);
|
|
319
|
-
const stats = { size: 0 };
|
|
320
|
-
stats.tables = Object.fromEntries(data.map(({ TABLE_NAME: name, TABLE_ROWS: count, DATA_LENGTH: size }) => {
|
|
321
|
-
stats.size += size;
|
|
322
|
-
return [name, { count, size }];
|
|
323
|
-
}));
|
|
324
|
-
return stats;
|
|
325
|
-
}
|
|
326
|
-
async get(name, query, modifier) {
|
|
327
|
-
await this._tableTasks[name];
|
|
328
|
-
const filter = this._createFilter(name, query);
|
|
329
|
-
if (filter === "0")
|
|
330
|
-
return [];
|
|
331
|
-
const { fields, limit, offset, sort } = this.resolveModifier(name, modifier);
|
|
332
|
-
const keys = this._joinKeys(this._inferFields(name, fields));
|
|
333
|
-
let sql = `SELECT ${keys} FROM ${name} _${name} WHERE ${filter}`;
|
|
334
|
-
if (sort)
|
|
335
|
-
sql += " ORDER BY " + Object.entries(sort).map(([key, order]) => `${backtick(key)} ${order}`).join(", ");
|
|
336
|
-
if (limit)
|
|
337
|
-
sql += " LIMIT " + limit;
|
|
338
|
-
if (offset)
|
|
339
|
-
sql += " OFFSET " + offset;
|
|
340
|
-
return this.queue(sql).then((data) => {
|
|
341
|
-
return data.map((row) => this.model.parse(name, row));
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
toUpdateExpr(name, item, field, upsert) {
|
|
345
|
-
const escaped = backtick(field);
|
|
346
|
-
if (field in item) {
|
|
347
|
-
if ((0, import_orm.isEvalExpr)(item[field]) || !upsert) {
|
|
348
|
-
return this.sql.parseEval(item[field], name, field);
|
|
349
|
-
} else {
|
|
350
|
-
return `VALUES(${escaped})`;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
const valueInit = `ifnull(${escaped}, '{}')`;
|
|
354
|
-
let value = valueInit;
|
|
355
|
-
for (const key in item) {
|
|
356
|
-
if (!key.startsWith(field + "."))
|
|
357
|
-
continue;
|
|
358
|
-
const rest = key.slice(field.length + 1).split(".");
|
|
359
|
-
value = `json_set(${value}, '$${rest.map((key2) => `."${key2}"`).join("")}', ${this.sql.parseEval(item[key])})`;
|
|
360
|
-
}
|
|
361
|
-
if (value === valueInit) {
|
|
362
|
-
return escaped;
|
|
363
|
-
} else {
|
|
364
|
-
return value;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
async set(name, query, data) {
|
|
368
|
-
data = this.model.format(name, data);
|
|
369
|
-
const { fields } = this.resolveTable(name);
|
|
370
|
-
await this._tableTasks[name];
|
|
371
|
-
const filter = this._createFilter(name, query);
|
|
372
|
-
if (filter === "0")
|
|
373
|
-
return;
|
|
374
|
-
const updateFields = [...new Set(Object.keys(data).map((key) => {
|
|
375
|
-
return Object.keys(fields).find((field) => field === key || key.startsWith(field + "."));
|
|
376
|
-
}))];
|
|
377
|
-
const update = updateFields.map((field) => {
|
|
378
|
-
const escaped = backtick(field);
|
|
379
|
-
return `${escaped} = ${this.toUpdateExpr(name, data, field, false)}`;
|
|
380
|
-
}).join(", ");
|
|
381
|
-
await this.query(`UPDATE ${name} SET ${update} WHERE ${filter}`);
|
|
382
|
-
}
|
|
383
|
-
async remove(name, query) {
|
|
384
|
-
await this._tableTasks[name];
|
|
385
|
-
const filter = this._createFilter(name, query);
|
|
386
|
-
if (filter === "0")
|
|
387
|
-
return;
|
|
388
|
-
await this.query("DELETE FROM ?? WHERE " + filter, [name]);
|
|
389
|
-
}
|
|
390
|
-
async create(name, data) {
|
|
391
|
-
await this._tableTasks[name];
|
|
392
|
-
data = this.model.create(name, data);
|
|
393
|
-
const formatted = this.model.format(name, data);
|
|
394
|
-
const { autoInc, primary } = this.resolveTable(name);
|
|
395
|
-
const keys = Object.keys(formatted);
|
|
396
|
-
const header = await this.query(`INSERT INTO ?? (${this._joinKeys(keys)}) VALUES (${keys.map(() => "?").join(", ")})`, [name, ...this._formatValues(name, formatted, keys)]);
|
|
397
|
-
if (!autoInc)
|
|
398
|
-
return data;
|
|
399
|
-
return __spreadProps(__spreadValues({}, data), { [primary]: header.insertId });
|
|
400
|
-
}
|
|
401
|
-
async upsert(name, data, keys) {
|
|
402
|
-
if (!data.length)
|
|
403
|
-
return;
|
|
404
|
-
data = data.map((item) => this.model.format(name, item));
|
|
405
|
-
await this._tableTasks[name];
|
|
406
|
-
const { fields, primary } = this.resolveTable(name);
|
|
407
|
-
const merged = {};
|
|
408
|
-
const insertion = data.map((item) => {
|
|
409
|
-
Object.assign(merged, item);
|
|
410
|
-
return this.model.format(name, (0, import_orm.executeUpdate)(this.model.create(name), item));
|
|
411
|
-
});
|
|
412
|
-
const indexFields = (0, import_koishi.makeArray)(keys || primary);
|
|
413
|
-
const dataFields = [...new Set(Object.keys(merged).map((key) => {
|
|
414
|
-
return Object.keys(fields).find((field) => field === key || key.startsWith(field + "."));
|
|
415
|
-
}))];
|
|
416
|
-
const updateFields = (0, import_koishi.difference)(dataFields, indexFields);
|
|
417
|
-
const createFilter = /* @__PURE__ */ __name((item) => this.sql.parseQuery((0, import_koishi.pick)(item, indexFields)), "createFilter");
|
|
418
|
-
const createMultiFilter = /* @__PURE__ */ __name((items) => {
|
|
419
|
-
if (items.length === 1) {
|
|
420
|
-
return createFilter(items[0]);
|
|
421
|
-
} else if (indexFields.length === 1) {
|
|
422
|
-
const key = indexFields[0];
|
|
423
|
-
return this.sql.parseQuery({ [key]: items.map((item) => item[key]) });
|
|
424
|
-
} else {
|
|
425
|
-
return items.map(createFilter).join(" OR ");
|
|
426
|
-
}
|
|
427
|
-
}, "createMultiFilter");
|
|
428
|
-
const update = updateFields.map((field) => {
|
|
429
|
-
const escaped = backtick(field);
|
|
430
|
-
const branches = {};
|
|
431
|
-
data.forEach((item) => {
|
|
432
|
-
var _a, _b;
|
|
433
|
-
((_b = branches[_a = this.toUpdateExpr(name, item, field, true)]) != null ? _b : branches[_a] = []).push(item);
|
|
434
|
-
});
|
|
435
|
-
const entries = Object.entries(branches).map(([expr, items]) => [createMultiFilter(items), expr]).sort(([a], [b]) => a.length - b.length).reverse();
|
|
436
|
-
let value = entries[0][1];
|
|
437
|
-
for (let index = 1; index < entries.length; index++) {
|
|
438
|
-
value = `if(${entries[index][0]}, ${entries[index][1]}, ${value})`;
|
|
439
|
-
}
|
|
440
|
-
return `${escaped} = ${value}`;
|
|
441
|
-
}).join(", ");
|
|
442
|
-
const initFields = Object.keys(fields);
|
|
443
|
-
const placeholder = `(${initFields.map(() => "?").join(", ")})`;
|
|
444
|
-
await this.query(`INSERT INTO ${this.sql.escapeId(name)} (${this._joinKeys(initFields)}) VALUES ${data.map(() => placeholder).join(", ")}
|
|
445
|
-
ON DUPLICATE KEY UPDATE ${update}`, [].concat(...insertion.map((item) => this._formatValues(name, item, initFields))));
|
|
446
|
-
}
|
|
447
|
-
async eval(name, expr, query) {
|
|
448
|
-
await this._tableTasks[name];
|
|
449
|
-
const filter = this._createFilter(name, query);
|
|
450
|
-
const output = this.sql.parseEval(expr);
|
|
451
|
-
const [data] = await this.queue(`SELECT ${output} AS value FROM ${name} WHERE ${filter}`);
|
|
452
|
-
return data.value;
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
__name(MysqlDatabase, "MysqlDatabase");
|
|
456
|
-
(function(MysqlDatabase2) {
|
|
457
|
-
MysqlDatabase2.Config = import_koishi.Schema.object({
|
|
458
|
-
host: import_koishi.Schema.string().description("要连接到的主机名。").default("localhost"),
|
|
459
|
-
port: import_koishi.Schema.natural().max(65535).description("要连接到的端口号。").default(3306),
|
|
460
|
-
user: import_koishi.Schema.string().description("要使用的用户名。").default("root"),
|
|
461
|
-
password: import_koishi.Schema.string().description("要使用的密码。").role("secret"),
|
|
462
|
-
database: import_koishi.Schema.string().description("要访问的数据库名。").default("koishi")
|
|
463
|
-
});
|
|
464
|
-
MysqlDatabase2.tables = {
|
|
465
|
-
user: {},
|
|
466
|
-
channel: {}
|
|
467
|
-
};
|
|
468
|
-
})(MysqlDatabase || (MysqlDatabase = {}));
|
|
469
|
-
var src_default = MysqlDatabase;
|
|
47
|
+
__name(apply, "apply");
|
|
470
48
|
// Annotate the CommonJS export names for ESM import in node:
|
|
471
|
-
0 && (module.exports = {
|
|
49
|
+
0 && (module.exports = {
|
|
50
|
+
Config,
|
|
51
|
+
apply,
|
|
52
|
+
name
|
|
53
|
+
});
|
|
472
54
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { createPool, escapeId, format, escape as mysqlEscape } from '@vlasky/mysql'\nimport type { OkPacket, Pool, PoolConfig } from 'mysql'\nimport { Context, Database, Dict, difference, DriverError, Logger, makeArray, pick, Schema, Tables, Time } from 'koishi'\nimport { Driver, executeUpdate, isEvalExpr, Model, Modifier, Query } from '@koishijs/orm'\nimport { Builder } from '@koishijs/sql-utils'\n\ndeclare module 'mysql' {\n interface UntypedFieldInfo {\n packet: UntypedFieldInfo\n }\n}\n\ndeclare module 'koishi' {\n interface Database {\n mysql: MysqlDatabase\n }\n\n interface Modules {\n 'database-mysql': typeof import('.')\n }\n}\n\nconst logger = new Logger('mysql')\n\nconst DEFAULT_DATE = new Date('1970-01-01')\n\nfunction getIntegerType(length = 11) {\n if (length <= 4) return 'tinyint'\n if (length <= 6) return 'smallint'\n if (length <= 9) return 'mediumint'\n if (length <= 11) return 'int'\n return 'bigint'\n}\n\nfunction getTypeDefinition({ type, length, precision, scale }: Model.Field) {\n switch (type) {\n case 'float':\n case 'double':\n case 'date':\n case 'time': return type\n case 'timestamp': return 'datetime'\n case 'integer': return getIntegerType(length)\n case 'unsigned': return `${getIntegerType(length)} unsigned`\n case 'decimal': return `decimal(${precision}, ${scale}) unsigned`\n case 'char': return `char(${length || 255})`\n case 'string': return `varchar(${length || 255})`\n case 'text': return `text(${length || 65535})`\n case 'list': return `text(${length || 65535})`\n case 'json': return `text(${length || 65535})`\n }\n}\n\nfunction backtick(str: string) {\n return '`' + str + '`'\n}\n\nfunction createIndex(keys: string | string[]) {\n return makeArray(keys).map(backtick).join(', ')\n}\n\nclass MySQLBuilder extends Builder {\n constructor(private model: Model) {\n super()\n }\n\n format(sql: string, values: any[], stringifyObjects?: boolean, timeZone?: string) {\n return format(sql, values, stringifyObjects, timeZone)\n }\n\n escapeId(value: string, forbidQualified?: boolean) {\n return escapeId(value, forbidQualified)\n }\n\n escape(value: any, table?: string, field?: string) {\n return mysqlEscape(this.stringify(value, table, field))\n }\n\n stringify(value: any, table?: string, field?: string) {\n const type = MysqlDatabase.tables[table]?.[field]\n if (typeof type === 'object') return type.stringify(value)\n\n const meta = this.model.config[table]?.fields[field]\n if (meta?.type === 'json') {\n return JSON.stringify(value)\n } else if (meta?.type === 'list') {\n return value.join(',')\n } else if (Model.Field.date.includes(meta?.type)) {\n return Time.template('yyyy-MM-dd hh:mm:ss', value)\n }\n\n return value\n }\n}\n\ninterface QueryTask {\n sql: string\n resolve: (value: any) => void\n reject: (error: Error) => void\n}\n\nclass MysqlDatabase extends Database {\n public pool: Pool\n public config: MysqlDatabase.Config\n\n mysql = this\n sql: MySQLBuilder\n\n private _tableTasks: Dict<Promise<any>> = {}\n private _queryTasks: QueryTask[] = []\n\n constructor(public ctx: Context, config?: MysqlDatabase.Config) {\n super(ctx)\n\n this.config = {\n host: 'localhost',\n port: 3306,\n user: 'root',\n database: 'koishi',\n charset: 'utf8mb4_general_ci',\n multipleStatements: true,\n typeCast: (field, next) => {\n const { orgName, orgTable } = field.packet\n const type = MysqlDatabase.tables[orgTable]?.[orgName]\n if (typeof type === 'object') return type.parse(field)\n\n const meta = this.model.config[orgTable]?.fields[orgName]\n if (Model.Field.string.includes(meta?.type)) {\n return field.string()\n } else if (meta?.type === 'json') {\n const source = field.string()\n return source ? JSON.parse(source) : meta.initial\n } else if (meta?.type === 'list') {\n const source = field.string()\n return source ? source.split(',') : []\n } else if (meta?.type === 'time') {\n const source = field.string()\n if (!source) return meta.initial\n const time = new Date(DEFAULT_DATE)\n const [h, m, s] = source.split(':')\n time.setHours(parseInt(h))\n time.setMinutes(parseInt(m))\n time.setSeconds(parseInt(s))\n return time\n }\n\n if (field.type === 'BIT') {\n return Boolean(field.buffer()?.readUInt8(0))\n } else {\n return next()\n }\n },\n ...config,\n }\n\n this.sql = new MySQLBuilder(this.model)\n }\n\n async start() {\n this.pool = createPool(this.config)\n\n for (const name in this.model.config) {\n this._tableTasks[name] = this._syncTable(name as keyof Tables)\n }\n\n this.ctx.on('model', (name) => {\n this._tableTasks[name] = this._syncTable(name)\n })\n }\n\n stop() {\n this.pool.end()\n }\n\n private _getColDefs(name: keyof Tables, columns: string[]) {\n const table = this.resolveTable(name)\n const { primary, foreign, autoInc } = table\n const fields = { ...table.fields }\n const unique = [...table.unique]\n const result: string[] = []\n\n // orm definitions\n for (const key in fields) {\n if (columns.includes(key)) continue\n const { initial, nullable = true } = fields[key]\n let def = backtick(key)\n if (key === primary && autoInc) {\n def += ' int unsigned not null auto_increment'\n } else {\n const typedef = getTypeDefinition(fields[key])\n def += ' ' + typedef\n if (makeArray(primary).includes(key)) {\n def += ' not null'\n } else {\n def += (nullable ? ' ' : ' not ') + 'null'\n }\n // blob, text, geometry or json columns cannot have default values\n if (initial && !typedef.startsWith('text')) {\n def += ' default ' + this.sql.escape(initial, name, key)\n }\n }\n result.push(def)\n }\n\n if (!columns.length) {\n result.push(`primary key (${createIndex(primary)})`)\n for (const key of unique) {\n result.push(`unique index (${createIndex(key)})`)\n }\n for (const key in foreign) {\n const [table, key2] = foreign[key]\n result.push(`foreign key (${backtick(key)}) references ${escapeId(table)} (${backtick(key2)})`)\n }\n }\n\n return result\n }\n\n /** synchronize table schema */\n private async _syncTable(name: keyof Tables) {\n await this._tableTasks[name]\n // eslint-disable-next-line max-len\n const data = await this.queue<any[]>('SELECT COLUMN_NAME from information_schema.columns WHERE TABLE_SCHEMA = ? && TABLE_NAME = ?', [this.config.database, name])\n const columns = data.map(row => row.COLUMN_NAME)\n const result = this._getColDefs(name, columns)\n if (!columns.length) {\n logger.info('auto creating table %c', name)\n await this.queue(`CREATE TABLE ?? (${result.join(',')}) COLLATE = ?`, [name, this.config.charset])\n } else if (result.length) {\n logger.info('auto updating table %c', name)\n await this.queue(`ALTER TABLE ?? ${result.map(def => 'ADD ' + def).join(',')}`, [name])\n }\n }\n\n _inferFields<T extends keyof Tables>(table: T, keys: readonly string[]) {\n if (!keys) return\n const types = MysqlDatabase.tables[table] || {}\n return keys.map((key) => {\n const type = types[key]\n return typeof type === 'function' ? `${type()} AS ${key}` : key\n }) as (keyof Tables[T])[]\n }\n\n _createFilter(name: keyof Tables, query: Query) {\n return this.sql.parseQuery(this.resolveQuery(name, query))\n }\n\n _joinKeys = (keys: readonly string[]) => {\n return keys ? keys.map(key => key.includes('`') ? key : `\\`${key}\\``).join(',') : '*'\n }\n\n _formatValues = (table: string, data: object, keys: readonly string[]) => {\n return keys.map((key) => this.sql.stringify(data[key], table as never, key))\n }\n\n query<T = any>(sql: string, values?: any): Promise<T> {\n const error = new Error()\n return new Promise((resolve, reject) => {\n sql = format(sql, values)\n logger.debug('[sql]', sql)\n this.pool.query(sql, (err: Error, results) => {\n if (!err) return resolve(results)\n logger.warn(sql)\n if (err['code'] === 'ER_DUP_ENTRY') {\n err = new DriverError('duplicate-entry', err.message)\n }\n err.stack = err.message + error.stack.slice(5)\n reject(err)\n })\n })\n }\n\n queue<T = any>(sql: string, values?: any): Promise<T> {\n if (!this.config.multipleStatements) {\n return this.query(sql, values)\n }\n\n sql = format(sql, values)\n return new Promise<any>((resolve, reject) => {\n this._queryTasks.push({ sql, resolve, reject })\n process.nextTick(() => this._flushTasks())\n })\n }\n\n private async _flushTasks() {\n const tasks = this._queryTasks\n if (!tasks.length) return\n this._queryTasks = []\n\n try {\n let results = await this.query(tasks.map(task => task.sql).join('; '))\n if (tasks.length === 1) results = [results]\n tasks.forEach((task, index) => {\n task.resolve(results[index])\n })\n } catch (error) {\n tasks.forEach(task => task.reject(error))\n }\n }\n\n select<T extends {}>(table: string, fields: readonly (string & keyof T)[], conditional?: string, values?: readonly any[]): Promise<T[]>\n select(table: string, fields: string[], conditional?: string, values: readonly any[] = []) {\n logger.debug(`[select] ${table}: ${fields ? fields.join(', ') : '*'}`)\n const sql = 'SELECT '\n + this._joinKeys(fields)\n + (table.includes('.') ? `FROM ${table}` : ' FROM `' + table + `\\` _${table}`)\n + (conditional ? ' WHERE ' + conditional : '')\n return this.queue(sql, values)\n }\n\n async drop() {\n const data = await this.select('information_schema.tables', ['TABLE_NAME'], 'TABLE_SCHEMA = ?', [this.config.database])\n if (!data.length) return\n await this.query(data.map(({ TABLE_NAME }) => `DROP TABLE ${this.sql.escapeId(TABLE_NAME)}`).join('; '))\n }\n\n async stats() {\n const data = await this.select('information_schema.tables', ['TABLE_NAME', 'TABLE_ROWS', 'DATA_LENGTH'], 'TABLE_SCHEMA = ?', [this.config.database])\n const stats: Driver.Stats = { size: 0 }\n stats.tables = Object.fromEntries(data.map(({ TABLE_NAME: name, TABLE_ROWS: count, DATA_LENGTH: size }) => {\n stats.size += size\n return [name, { count, size }]\n }))\n return stats\n }\n\n async get(name: keyof Tables, query: Query, modifier?: Modifier) {\n await this._tableTasks[name]\n const filter = this._createFilter(name, query)\n if (filter === '0') return []\n const { fields, limit, offset, sort } = this.resolveModifier(name, modifier)\n const keys = this._joinKeys(this._inferFields(name, fields))\n let sql = `SELECT ${keys} FROM ${name} _${name} WHERE ${filter}`\n if (sort) sql += ' ORDER BY ' + Object.entries(sort).map(([key, order]) => `${backtick(key)} ${order}`).join(', ')\n if (limit) sql += ' LIMIT ' + limit\n if (offset) sql += ' OFFSET ' + offset\n return this.queue(sql).then((data) => {\n return data.map((row) => this.model.parse(name, row))\n })\n }\n\n private toUpdateExpr(name: string, item: any, field: string, upsert: boolean) {\n const escaped = backtick(field)\n\n // update directly\n if (field in item) {\n if (isEvalExpr(item[field]) || !upsert) {\n return this.sql.parseEval(item[field], name, field)\n } else {\n return `VALUES(${escaped})`\n }\n }\n\n // update with json_set\n const valueInit = `ifnull(${escaped}, '{}')`\n let value = valueInit\n for (const key in item) {\n if (!key.startsWith(field + '.')) continue\n const rest = key.slice(field.length + 1).split('.')\n value = `json_set(${value}, '$${rest.map(key => `.\"${key}\"`).join('')}', ${this.sql.parseEval(item[key])})`\n }\n\n if (value === valueInit) {\n return escaped\n } else {\n return value\n }\n }\n\n async set(name: keyof Tables, query: Query, data: {}) {\n data = this.model.format(name, data)\n const { fields } = this.resolveTable(name)\n await this._tableTasks[name]\n const filter = this._createFilter(name, query)\n if (filter === '0') return\n const updateFields = [...new Set(Object.keys(data).map((key) => {\n return Object.keys(fields).find(field => field === key || key.startsWith(field + '.'))\n }))]\n\n const update = updateFields.map((field) => {\n const escaped = backtick(field)\n return `${escaped} = ${this.toUpdateExpr(name, data, field, false)}`\n }).join(', ')\n\n await this.query(`UPDATE ${name} SET ${update} WHERE ${filter}`)\n }\n\n async remove(name: keyof Tables, query: Query) {\n await this._tableTasks[name]\n const filter = this._createFilter(name, query)\n if (filter === '0') return\n await this.query('DELETE FROM ?? WHERE ' + filter, [name])\n }\n\n async create<T extends keyof Tables>(name: T, data: {}) {\n await this._tableTasks[name]\n data = this.model.create(name, data)\n const formatted = this.model.format(name, data)\n const { autoInc, primary } = this.resolveTable(name)\n const keys = Object.keys(formatted)\n const header = await this.query<OkPacket>(\n `INSERT INTO ?? (${this._joinKeys(keys)}) VALUES (${keys.map(() => '?').join(', ')})`,\n [name, ...this._formatValues(name, formatted, keys)],\n )\n if (!autoInc) return data as any\n return { ...data, [primary as string]: header.insertId } as any\n }\n\n async upsert(name: keyof Tables, data: any[], keys: string | string[]) {\n if (!data.length) return\n data = data.map(item => this.model.format(name, item))\n await this._tableTasks[name]\n\n const { fields, primary } = this.resolveTable(name)\n const merged = {}\n const insertion = data.map((item) => {\n Object.assign(merged, item)\n return this.model.format(name, executeUpdate(this.model.create(name), item))\n })\n const indexFields = makeArray(keys || primary)\n const dataFields = [...new Set(Object.keys(merged).map((key) => {\n return Object.keys(fields).find(field => field === key || key.startsWith(field + '.'))\n }))]\n const updateFields = difference(dataFields, indexFields)\n\n const createFilter = (item: any) => this.sql.parseQuery(pick(item, indexFields))\n const createMultiFilter = (items: any[]) => {\n if (items.length === 1) {\n return createFilter(items[0])\n } else if (indexFields.length === 1) {\n const key = indexFields[0]\n return this.sql.parseQuery({ [key]: items.map(item => item[key]) })\n } else {\n return items.map(createFilter).join(' OR ')\n }\n }\n\n const update = updateFields.map((field) => {\n const escaped = backtick(field)\n const branches: Dict<any[]> = {}\n data.forEach((item) => {\n (branches[this.toUpdateExpr(name, item, field, true)] ??= []).push(item)\n })\n\n const entries = Object.entries(branches)\n .map(([expr, items]) => [createMultiFilter(items), expr])\n .sort(([a], [b]) => a.length - b.length)\n .reverse()\n\n let value = entries[0][1]\n for (let index = 1; index < entries.length; index++) {\n value = `if(${entries[index][0]}, ${entries[index][1]}, ${value})`\n }\n return `${escaped} = ${value}`\n }).join(', ')\n\n const initFields = Object.keys(fields)\n const placeholder = `(${initFields.map(() => '?').join(', ')})`\n await this.query(\n `INSERT INTO ${this.sql.escapeId(name)} (${this._joinKeys(initFields)}) VALUES ${data.map(() => placeholder).join(', ')}\n ON DUPLICATE KEY UPDATE ${update}`,\n [].concat(...insertion.map(item => this._formatValues(name, item, initFields))),\n )\n }\n\n async eval(name: keyof Tables, expr: any, query: Query) {\n await this._tableTasks[name]\n const filter = this._createFilter(name, query)\n const output = this.sql.parseEval(expr)\n const [data] = await this.queue(`SELECT ${output} AS value FROM ${name} WHERE ${filter}`)\n return data.value\n }\n}\n\nnamespace MysqlDatabase {\n export interface Config extends PoolConfig {}\n\n export const Config = Schema.object({\n host: Schema.string().description('要连接到的主机名。').default('localhost'),\n port: Schema.natural().max(65535).description('要连接到的端口号。').default(3306),\n user: Schema.string().description('要使用的用户名。').default('root'),\n password: Schema.string().description('要使用的密码。').role('secret'),\n database: Schema.string().description('要访问的数据库名。').default('koishi'),\n })\n\n type Declarations = {\n [T in keyof Tables]?: {\n [K in keyof Tables[T]]?: () => string\n }\n }\n\n /**\n * @deprecated use `import('koishi').Field` instead\n */\n export const tables: Declarations = {\n user: {},\n channel: {},\n }\n}\n\nexport default MysqlDatabase\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { Context, Schema } from 'koishi'\nimport MySQLDriver from '@cosmotype/driver-mysql'\n\nexport const name = 'MemoryDatabase'\n\nexport type Config = MySQLDriver.Config\n\nexport const Config: Schema<Config> = Schema.object({\n host: Schema.string().description('要连接到的主机名。').default('localhost'),\n port: Schema.natural().max(65535).description('要连接到的端口号。').default(3306),\n user: Schema.string().description('要使用的用户名。').default('root'),\n password: Schema.string().description('要使用的密码。').role('secret'),\n database: Schema.string().description('要访问的数据库名。').default('koishi'),\n})\n\nexport function apply(ctx: Context, config: Config) {\n const driver = new MySQLDriver(ctx.model, config)\n ctx.on('ready', () => driver.start())\n ctx.on('dispose', () => driver.stop())\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAgC;AAChC,0BAAwB;AAEjB,IAAM,OAAO;AAIb,IAAM,SAAyB,qBAAO,OAAO;AAAA,EAClD,MAAM,qBAAO,SAAS,YAAY,aAAa,QAAQ;AAAA,EACvD,MAAM,qBAAO,UAAU,IAAI,OAAO,YAAY,aAAa,QAAQ;AAAA,EACnE,MAAM,qBAAO,SAAS,YAAY,YAAY,QAAQ;AAAA,EACtD,UAAU,qBAAO,SAAS,YAAY,WAAW,KAAK;AAAA,EACtD,UAAU,qBAAO,SAAS,YAAY,aAAa,QAAQ;AAAA;AAGtD,eAAe,KAAc,QAAgB;AAClD,QAAM,SAAS,IAAI,4BAAY,IAAI,OAAO;AAC1C,MAAI,GAAG,SAAS,MAAM,OAAO;AAC7B,MAAI,GAAG,WAAW,MAAM,OAAO;AAAA;AAHjB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koishijs/plugin-database-mysql",
|
|
3
3
|
"description": "MySQL support for Koishi",
|
|
4
|
-
"version": "4.2
|
|
4
|
+
"version": "4.3.2",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -14,12 +14,13 @@
|
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/koishijs/koishi.git"
|
|
17
|
+
"url": "git+https://github.com/koishijs/koishi.git",
|
|
18
|
+
"directory": "plugins/database/mysql"
|
|
18
19
|
},
|
|
19
20
|
"bugs": {
|
|
20
21
|
"url": "https://github.com/koishijs/koishi/issues"
|
|
21
22
|
},
|
|
22
|
-
"homepage": "https://koishi.js.org/
|
|
23
|
+
"homepage": "https://koishi.js.org/plugins/database/mysql.html",
|
|
23
24
|
"keywords": [
|
|
24
25
|
"bot",
|
|
25
26
|
"chatbot",
|
|
@@ -28,17 +29,10 @@
|
|
|
28
29
|
"mysql",
|
|
29
30
|
"impl:database"
|
|
30
31
|
],
|
|
31
|
-
"devDependencies": {
|
|
32
|
-
"@koishijs/database-tests": "^1.0.0",
|
|
33
|
-
"@koishijs/plugin-mock": "^1.0.3"
|
|
34
|
-
},
|
|
35
32
|
"peerDependencies": {
|
|
36
|
-
"koishi": "^4.
|
|
33
|
+
"koishi": "^4.6.2"
|
|
37
34
|
},
|
|
38
35
|
"dependencies": {
|
|
39
|
-
"@
|
|
40
|
-
"@koishijs/sql-utils": "^1.1.0",
|
|
41
|
-
"@types/mysql": "^2.15.21",
|
|
42
|
-
"@vlasky/mysql": "^2.18.5"
|
|
36
|
+
"@cosmotype/driver-mysql": "^1.0.5"
|
|
43
37
|
}
|
|
44
38
|
}
|