@zhin.js/database 1.1.2 → 1.1.4
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 +16 -0
- package/lib/dialects/mysql.d.ts +1 -0
- package/lib/dialects/mysql.js +38 -7
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -108,6 +108,22 @@ await db.start();
|
|
|
108
108
|
|
|
109
109
|
### MySQL Example
|
|
110
110
|
|
|
111
|
+
框架初始化验证基线为 **MySQL 8.4 LTS**,驱动为 `mysql2` 3.x。项目配置中的
|
|
112
|
+
`database.dialect: mysql` 用于选择方言,由 CLI 移除后再把连接参数传给驱动。
|
|
113
|
+
不要为了消除驱动警告而删除项目配置中的 `dialect`。
|
|
114
|
+
|
|
115
|
+
MySQL 方言将 `text` 的非空默认值和 `json` 默认值写成表达式,例如
|
|
116
|
+
`TEXT DEFAULT ('')`;普通 `TEXT DEFAULT ''` 会被 MySQL 拒绝。
|
|
117
|
+
声明了 `length` 的 `text` 使用 `VARCHAR(length)`;未声明长度的文本主键或唯一键
|
|
118
|
+
使用 `VARCHAR(768)`,以完整值建立索引;显式声明的索引文本长度超过 768 时直接报错。文本主键和唯一键使用 `utf8mb4_0900_bin`,保留大小写及末尾空格的区别。其余文本仍为 `TEXT`。
|
|
119
|
+
新表显式使用 InnoDB `ROW_FORMAT=DYNAMIC`。使用 4KB / 8KB InnoDB 页时,启动会先校验文本索引长度,并提示配置更大的页或显式声明更短的键长度。
|
|
120
|
+
键值应在该长度内;需要更短的业务边界时显式声明 `length`。这里没有使用文本前缀唯一索引,
|
|
121
|
+
避免将前缀相同的不同键误判为重复。
|
|
122
|
+
|
|
123
|
+
如果启动报 `message_key can't have a default value`,请升级包含此修复的
|
|
124
|
+
`@zhin.js/database` 与 `@zhin.js/cli` 后重启。该错误来自建表 SQL,无需降级
|
|
125
|
+
MySQL 或 mysql2,也无需删除已有数据库。修复针对新建表,不会缩窄既有文本列。
|
|
126
|
+
|
|
111
127
|
```typescript
|
|
112
128
|
const db = Registry.create('mysql', {
|
|
113
129
|
host: 'localhost',
|
package/lib/dialects/mysql.d.ts
CHANGED
|
@@ -64,4 +64,5 @@ export declare class MySQLDialect<S extends Record<string, object> = Record<stri
|
|
|
64
64
|
}
|
|
65
65
|
export declare class MySQL<S extends Record<string, object> = Record<string, object>> extends RelatedDatabase<MySQLDialectConfig, S> {
|
|
66
66
|
constructor(config: MySQLDialectConfig, definitions?: Database.DefinitionObj<S>);
|
|
67
|
+
protected initialize(): Promise<void>;
|
|
67
68
|
}
|
package/lib/dialects/mysql.js
CHANGED
|
@@ -187,20 +187,36 @@ export class MySQLDialect extends Dialect {
|
|
|
187
187
|
return `LIMIT ${offset}, ${limit}`;
|
|
188
188
|
}
|
|
189
189
|
formatCreateTable(tableName, columns) {
|
|
190
|
-
return `CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(String(tableName))} (${columns.join(', ')}) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`;
|
|
190
|
+
return `CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(String(tableName))} (${columns.join(', ')}) ENGINE=InnoDB ROW_FORMAT=DYNAMIC DEFAULT CHARSET=utf8mb4`;
|
|
191
191
|
}
|
|
192
192
|
formatColumnDefinition(field, column) {
|
|
193
193
|
const name = this.quoteIdentifier(String(field));
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
// TEXT cannot be indexed in full. Bounded keys retain full-value uniqueness
|
|
195
|
+
// within InnoDB's 3072-byte index limit under utf8mb4 (4 bytes per character).
|
|
196
|
+
const boundedText = column.type.toLowerCase() === 'text'
|
|
197
|
+
&& (column.length !== undefined || column.primary || column.unique);
|
|
198
|
+
const indexedText = boundedText && (column.primary || column.unique);
|
|
199
|
+
if (boundedText && (!Number.isInteger(column.length ?? 768) || (column.length ?? 768) < 1)) {
|
|
200
|
+
throw new RangeError(`MySQL text column ${field} requires a positive integer length`);
|
|
201
|
+
}
|
|
202
|
+
if (indexedText && (column.length ?? 768) > 768) {
|
|
203
|
+
throw new RangeError(`MySQL indexed text column ${field} exceeds the utf8mb4 index limit of 768 characters`);
|
|
204
|
+
}
|
|
205
|
+
const collation = indexedText ? ' CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin' : '';
|
|
206
|
+
const type = boundedText ? 'VARCHAR' : this.mapColumnType(column.type);
|
|
207
|
+
const length = boundedText ? `(${column.length ?? 768})` : column.length ? `(${column.length})` : '';
|
|
196
208
|
const nullable = column.nullable === false ? ' NOT NULL' : '';
|
|
197
209
|
const primary = column.primary ? ' PRIMARY KEY' : '';
|
|
198
210
|
const unique = column.unique ? ' UNIQUE' : '';
|
|
199
211
|
const autoIncrement = column.autoIncrement ? ' AUTO_INCREMENT' : '';
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
212
|
+
let defaultVal = '';
|
|
213
|
+
if (column.default !== undefined) {
|
|
214
|
+
const value = this.formatDefaultValue(column.default);
|
|
215
|
+
// MySQL 8.0.13+ requires even literal TEXT/JSON defaults to be expressions.
|
|
216
|
+
const expression = (type === 'TEXT' || type === 'JSON') && column.default !== null;
|
|
217
|
+
defaultVal = ` DEFAULT ${expression ? `(${value})` : value}`;
|
|
218
|
+
}
|
|
219
|
+
return `${name} ${type}${length}${collation}${primary}${unique}${autoIncrement}${nullable}${defaultVal}`;
|
|
204
220
|
}
|
|
205
221
|
async reconcileDefinitions(definitions) {
|
|
206
222
|
const expected = new Map();
|
|
@@ -320,5 +336,20 @@ export class MySQL extends RelatedDatabase {
|
|
|
320
336
|
constructor(config, definitions) {
|
|
321
337
|
super(new MySQLDialect(config), definitions);
|
|
322
338
|
}
|
|
339
|
+
async initialize() {
|
|
340
|
+
const indexedText = [...this.definitions.values()].flatMap(definition => Object.entries(definition).filter(([, column]) => column.type.toLowerCase() === 'text' && (column.primary || column.unique)));
|
|
341
|
+
if (indexedText.length) {
|
|
342
|
+
const [settings] = await this.dialect.query('SELECT @@innodb_page_size AS page_size');
|
|
343
|
+
const maxLength = Math.min(768, Number(settings?.page_size) * 3 / 64);
|
|
344
|
+
if (!Number.isFinite(maxLength) || maxLength < 1)
|
|
345
|
+
throw new Error('Cannot determine MySQL InnoDB text index limit');
|
|
346
|
+
for (const [name, column] of indexedText) {
|
|
347
|
+
if ((column.length ?? 768) > maxLength) {
|
|
348
|
+
throw new RangeError(`MySQL indexed text column ${name} requires ${column.length ?? 768} characters, but innodb_page_size=${settings.page_size} allows ${maxLength}; use a 16KB-or-larger InnoDB page size or declare a smaller key length`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
await super.initialize();
|
|
353
|
+
}
|
|
323
354
|
}
|
|
324
355
|
Registry.register('mysql', MySQL);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/database",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Universal database abstraction layer for Zhin.js - supports SQLite, MySQL, PostgreSQL, MongoDB, Redis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -9,18 +9,17 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./lib/index.d.ts",
|
|
11
11
|
"import": "./lib/index.js",
|
|
12
|
-
"require": "./lib/index.js"
|
|
13
|
-
"development": "./src/index.ts"
|
|
12
|
+
"require": "./lib/index.js"
|
|
14
13
|
}
|
|
15
14
|
},
|
|
16
15
|
"files": [
|
|
17
16
|
"lib"
|
|
18
17
|
],
|
|
19
18
|
"engines": {
|
|
20
|
-
"node": ">=22.
|
|
19
|
+
"node": ">=22.19.0"
|
|
21
20
|
},
|
|
22
21
|
"dependencies": {
|
|
23
|
-
"@zhin.js/logger": "1.1.
|
|
22
|
+
"@zhin.js/logger": "1.1.2"
|
|
24
23
|
},
|
|
25
24
|
"peerDependencies": {
|
|
26
25
|
"mongodb": ">=3.1.13",
|