@maiyunnet/kebab 9.15.1 → 9.15.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/doc/kebab-rag.md +419 -179
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/db/conn.js +3 -0
- package/lib/db/value.d.ts +43 -0
- package/lib/db/value.js +59 -0
- package/lib/sql/value.d.ts +43 -0
- package/lib/sql/value.js +59 -0
- package/lib/sql.d.ts +11 -16
- package/lib/sql.js +72 -90
- package/lib/undici/response.d.ts +1 -1
- package/package.json +1 -1
- package/sys/mod.d.ts +12 -3
- package/sys/mod.js +56 -16
- package/www/example/ctr/test.js +21 -9
package/sys/mod.d.ts
CHANGED
|
@@ -44,6 +44,8 @@ export default class Mod {
|
|
|
44
44
|
protected static _$pre?: string;
|
|
45
45
|
/** --- 要 update 的内容 --- */
|
|
46
46
|
protected _updates: Record<string, boolean>;
|
|
47
|
+
/** --- 要在数据库边界序列化的 JSON 字段 --- */
|
|
48
|
+
protected _jsonUpdates: Record<string, boolean>;
|
|
47
49
|
/** --- 模型获取的属性 --- */
|
|
48
50
|
protected _data: Record<string, any>;
|
|
49
51
|
/** --- 当前选择的分表 _ 后缀,多个代表联查 --- */
|
|
@@ -97,7 +99,7 @@ export default class Mod {
|
|
|
97
99
|
'token': string;
|
|
98
100
|
'value': kebab.DbValue;
|
|
99
101
|
};
|
|
100
|
-
/** ---
|
|
102
|
+
/** --- 标记 JSON 字段;模型内保持原始对象,写入数据库时再序列化 --- */
|
|
101
103
|
static json<T>(obj: T): T;
|
|
102
104
|
/**
|
|
103
105
|
* --- 添加一个序列(允许超过 65536 的占位符会被拆分多次执行) ---
|
|
@@ -181,9 +183,10 @@ export default class Mod {
|
|
|
181
183
|
}): lSql.Sql;
|
|
182
184
|
/**
|
|
183
185
|
* --- 批量更新数据 ---
|
|
184
|
-
* @param db
|
|
186
|
+
* @param db 数据库对象;多批次需要整体原子性时应传入 Transaction
|
|
185
187
|
* @param data 数据列表,每个元素必须包含 key 字段,其余字段为要更新的列;
|
|
186
|
-
*
|
|
188
|
+
* 支持稀疏数据(不同元素可以拥有不同的列集合),内部会自动按列集合分组批量执行;
|
|
189
|
+
* 相同 key 会按输入顺序合并,后出现的同名字段覆盖先出现的值
|
|
187
190
|
* @param key 用于定位待更新记录的字段名,通常为主键或唯一键,至少必须建立索引;
|
|
188
191
|
* 该参数是字段名而不是索引名,仅参与 ON / WHERE 条件匹配,不会被更新;
|
|
189
192
|
* data 中每个元素都必须包含此字段,否则该元素会被跳过
|
|
@@ -304,6 +307,12 @@ export default class Mod {
|
|
|
304
307
|
* @param obj 要转换的 kv 数据列表
|
|
305
308
|
*/
|
|
306
309
|
static toArrayByRecord<T extends Mod>(obj: Record<string, T>): Record<string, Record<string, any>>;
|
|
310
|
+
/**
|
|
311
|
+
* --- 设置单个模型属性,并记录是否需要在数据库边界序列化 ---
|
|
312
|
+
* @param key 字段名
|
|
313
|
+
* @param value 字段值
|
|
314
|
+
*/
|
|
315
|
+
private _setProperty;
|
|
307
316
|
set<T extends this, TK extends keyof T>(n: Record<TK, T[TK] | undefined>): void;
|
|
308
317
|
set<T extends this, TK extends keyof T>(n: TK, v: T[TK]): void;
|
|
309
318
|
/**
|
package/sys/mod.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as lSql from '#kebab/lib/sql.js';
|
|
7
7
|
import * as lDb from '#kebab/lib/db.js';
|
|
8
|
+
import * as lSqlValue from '#kebab/lib/sql/value.js';
|
|
8
9
|
import * as lCore from '#kebab/lib/core.js';
|
|
9
10
|
import * as lText from '#kebab/lib/text.js';
|
|
10
11
|
/** --- 条数列表 --- */
|
|
@@ -58,6 +59,8 @@ export default class Mod {
|
|
|
58
59
|
static _$pre;
|
|
59
60
|
/** --- 要 update 的内容 --- */
|
|
60
61
|
_updates = {};
|
|
62
|
+
/** --- 要在数据库边界序列化的 JSON 字段 --- */
|
|
63
|
+
_jsonUpdates = {};
|
|
61
64
|
/** --- 模型获取的属性 --- */
|
|
62
65
|
_data = {};
|
|
63
66
|
/** --- 当前选择的分表 _ 后缀,多个代表联查 --- */
|
|
@@ -127,7 +130,7 @@ export default class Mod {
|
|
|
127
130
|
static value(val) {
|
|
128
131
|
return lSql.value(val);
|
|
129
132
|
}
|
|
130
|
-
/** ---
|
|
133
|
+
/** --- 标记 JSON 字段;模型内保持原始对象,写入数据库时再序列化 --- */
|
|
131
134
|
static json(obj) {
|
|
132
135
|
return lSql.json(obj);
|
|
133
136
|
}
|
|
@@ -308,9 +311,10 @@ export default class Mod {
|
|
|
308
311
|
}
|
|
309
312
|
/**
|
|
310
313
|
* --- 批量更新数据 ---
|
|
311
|
-
* @param db
|
|
314
|
+
* @param db 数据库对象;多批次需要整体原子性时应传入 Transaction
|
|
312
315
|
* @param data 数据列表,每个元素必须包含 key 字段,其余字段为要更新的列;
|
|
313
|
-
*
|
|
316
|
+
* 支持稀疏数据(不同元素可以拥有不同的列集合),内部会自动按列集合分组批量执行;
|
|
317
|
+
* 相同 key 会按输入顺序合并,后出现的同名字段覆盖先出现的值
|
|
314
318
|
* @param key 用于定位待更新记录的字段名,通常为主键或唯一键,至少必须建立索引;
|
|
315
319
|
* 该参数是字段名而不是索引名,仅参与 ON / WHERE 条件匹配,不会被更新;
|
|
316
320
|
* data 中每个元素都必须包含此字段,否则该元素会被跳过
|
|
@@ -320,9 +324,23 @@ export default class Mod {
|
|
|
320
324
|
if (!data.length) {
|
|
321
325
|
return true;
|
|
322
326
|
}
|
|
327
|
+
// --- 先按 key 合并,避免 UPDATE JOIN / UPDATE FROM 一对多匹配时结果不确定 ---
|
|
328
|
+
const items = new Map();
|
|
329
|
+
for (const item of data) {
|
|
330
|
+
if (!Object.hasOwn(item, key) || item[key] === undefined || item[key] === null) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const current = items.get(item[key]);
|
|
334
|
+
if (current) {
|
|
335
|
+
Object.assign(current, item);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
items.set(item[key], { ...item });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
323
341
|
// --- 按列集合分组(处理稀疏数据,保证每组内所有行的列完全一致)---
|
|
324
342
|
const groups = new Map();
|
|
325
|
-
for (const item of
|
|
343
|
+
for (const item of items.values()) {
|
|
326
344
|
const itemCols = Object.keys(item).filter(k => k !== key).sort();
|
|
327
345
|
if (itemCols.length === 0) {
|
|
328
346
|
continue;
|
|
@@ -343,10 +361,14 @@ export default class Mod {
|
|
|
343
361
|
const service = db.getService() ?? lDb.ESERVICE.PGSQL;
|
|
344
362
|
/** --- 每行占位符数量 = key + 所有列,分批避免超出数据库参数上限 --- */
|
|
345
363
|
const maxBatchSize = Math.floor(65000 / allCols.length);
|
|
346
|
-
// ---
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
const
|
|
364
|
+
// --- 控制单条 SQL 的解析、临时数据和持锁规模;PostgreSQL 不应默认吃满协议参数上限 ---
|
|
365
|
+
const defaultBatchSize = service === lDb.ESERVICE.MYSQL ? 200 : 1_000;
|
|
366
|
+
const requestedBatchSize = opt.batchSize;
|
|
367
|
+
const normalizedBatchSize = (requestedBatchSize !== undefined &&
|
|
368
|
+
Number.isSafeInteger(requestedBatchSize) &&
|
|
369
|
+
requestedBatchSize > 0) ?
|
|
370
|
+
requestedBatchSize : defaultBatchSize;
|
|
371
|
+
const batchSize = Math.max(1, Math.min(normalizedBatchSize, maxBatchSize));
|
|
350
372
|
for (let i = 0; i < groupItems.length; i += batchSize) {
|
|
351
373
|
const batch = groupItems.slice(i, i + batchSize);
|
|
352
374
|
const sq = lSql.get({
|
|
@@ -528,6 +550,25 @@ export default class Mod {
|
|
|
528
550
|
}
|
|
529
551
|
return rtn;
|
|
530
552
|
}
|
|
553
|
+
// --- 动态方法 ---
|
|
554
|
+
/**
|
|
555
|
+
* --- 设置单个模型属性,并记录是否需要在数据库边界序列化 ---
|
|
556
|
+
* @param key 字段名
|
|
557
|
+
* @param value 字段值
|
|
558
|
+
*/
|
|
559
|
+
_setProperty(key, value) {
|
|
560
|
+
const isJson = lSqlValue.isJson(value);
|
|
561
|
+
const rawValue = isJson ? lSqlValue.unwrapJson(value) : value;
|
|
562
|
+
this._updates[key] = true;
|
|
563
|
+
if (isJson) {
|
|
564
|
+
this._jsonUpdates[key] = true;
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
delete this._jsonUpdates[key];
|
|
568
|
+
}
|
|
569
|
+
this._data[key] = rawValue;
|
|
570
|
+
this[key] = rawValue;
|
|
571
|
+
}
|
|
531
572
|
/**
|
|
532
573
|
* --- 设置一个/多个属性,值为 undefined 则不会被更新 ---
|
|
533
574
|
* @param n 字符串或键/值
|
|
@@ -542,9 +583,7 @@ export default class Mod {
|
|
|
542
583
|
continue;
|
|
543
584
|
}
|
|
544
585
|
// --- 强制更新,因为有的可能就是要强制更新既然设置了 ---
|
|
545
|
-
this.
|
|
546
|
-
this._data[k] = v;
|
|
547
|
-
this[k] = v;
|
|
586
|
+
this._setProperty(k, v);
|
|
548
587
|
}
|
|
549
588
|
}
|
|
550
589
|
else {
|
|
@@ -555,9 +594,7 @@ export default class Mod {
|
|
|
555
594
|
if (typeof n !== 'string') {
|
|
556
595
|
return;
|
|
557
596
|
}
|
|
558
|
-
this.
|
|
559
|
-
this._data[n] = v;
|
|
560
|
-
this[n] = v;
|
|
597
|
+
this._setProperty(n, v);
|
|
561
598
|
}
|
|
562
599
|
}
|
|
563
600
|
/**
|
|
@@ -575,7 +612,7 @@ export default class Mod {
|
|
|
575
612
|
const cstr = this.constructor;
|
|
576
613
|
const updates = {};
|
|
577
614
|
for (const k in this._updates) {
|
|
578
|
-
updates[k] = this._data[k];
|
|
615
|
+
updates[k] = this._jsonUpdates[k] ? lSql.json(this._data[k]) : this._data[k];
|
|
579
616
|
}
|
|
580
617
|
let r = null;
|
|
581
618
|
if ((cstr._$key !== '') && (updates[cstr._$key] === undefined)) {
|
|
@@ -647,6 +684,7 @@ export default class Mod {
|
|
|
647
684
|
}
|
|
648
685
|
if (r.packet?.affected) {
|
|
649
686
|
this._updates = {};
|
|
687
|
+
this._jsonUpdates = {};
|
|
650
688
|
this._data[cstr._$primary] = r.packet.insert;
|
|
651
689
|
this[cstr._$primary] = this._data[cstr._$primary];
|
|
652
690
|
return true;
|
|
@@ -681,6 +719,7 @@ export default class Mod {
|
|
|
681
719
|
}
|
|
682
720
|
where[field] = this._data[field];
|
|
683
721
|
delete this._updates[field];
|
|
722
|
+
delete this._jsonUpdates[field];
|
|
684
723
|
}
|
|
685
724
|
return this.save(where);
|
|
686
725
|
}
|
|
@@ -730,7 +769,7 @@ export default class Mod {
|
|
|
730
769
|
}
|
|
731
770
|
const updates = {};
|
|
732
771
|
for (const k in this._updates) {
|
|
733
|
-
updates[k] = this._data[k];
|
|
772
|
+
updates[k] = this._jsonUpdates[k] ? lSql.json(this._data[k]) : this._data[k];
|
|
734
773
|
}
|
|
735
774
|
this._sql.update(cstr._$table + (this._index ? ('_' + this._index[0]) : ''), [updates]).where(where ?? {
|
|
736
775
|
[cstr._$primary]: this._data[cstr._$primary]
|
|
@@ -742,6 +781,7 @@ export default class Mod {
|
|
|
742
781
|
}
|
|
743
782
|
if (r.packet.affected) {
|
|
744
783
|
this._updates = {};
|
|
784
|
+
this._jsonUpdates = {};
|
|
745
785
|
return true;
|
|
746
786
|
}
|
|
747
787
|
else {
|
package/www/example/ctr/test.js
CHANGED
|
@@ -687,7 +687,7 @@ Result:<pre id="result">Nothing.</pre>` + this._getEnd();
|
|
|
687
687
|
{ 'x': 3, 'y': 3 },
|
|
688
688
|
{ 'x': 1, 'y': 1 },
|
|
689
689
|
],
|
|
690
|
-
'json': { 'x': { 'y': 'abc' } },
|
|
690
|
+
'json': mTest.json({ 'x': { 'y': 'abc' } }),
|
|
691
691
|
'time_add': time,
|
|
692
692
|
});
|
|
693
693
|
const result = await test.create();
|
|
@@ -704,7 +704,7 @@ test.set({
|
|
|
704
704
|
{ 'x': 3, 'y': 3 },
|
|
705
705
|
{ 'x': 1, 'y': 1 },
|
|
706
706
|
],
|
|
707
|
-
'json': { 'x': { 'y': 'abc' } },
|
|
707
|
+
'json': mTest.json({ 'x': { 'y': 'abc' } }),
|
|
708
708
|
'time_add': time,
|
|
709
709
|
});
|
|
710
710
|
const result = await test.create();
|
|
@@ -800,7 +800,7 @@ await ft.save();</pre>`);
|
|
|
800
800
|
{ 'x': 7, 'y': 3 },
|
|
801
801
|
{ 'x': 5, 'y': 1 }
|
|
802
802
|
],
|
|
803
|
-
'json': { 'x': { 'y': 'def' } }
|
|
803
|
+
'json': mTest.json({ 'x': { 'y': 'def' } })
|
|
804
804
|
});
|
|
805
805
|
await ft.save();
|
|
806
806
|
await ft.refresh();
|
|
@@ -815,7 +815,7 @@ await ft.save();</pre>`);
|
|
|
815
815
|
{ 'x': 7, 'y': 3 },
|
|
816
816
|
{ 'x': 5, 'y': 1 }
|
|
817
817
|
],
|
|
818
|
-
'json': { 'x': { 'y': 'def' } }
|
|
818
|
+
'json': mTest.json({ 'x': { 'y': 'def' } })
|
|
819
819
|
});
|
|
820
820
|
await ft.save();
|
|
821
821
|
await ft.refresh();</pre>`);
|
|
@@ -2672,7 +2672,7 @@ Result:<pre id="result">Nothing.</pre>`);
|
|
|
2672
2672
|
{ 'x': 3, 'y': 3 },
|
|
2673
2673
|
{ 'x': 1, 'y': 1 }
|
|
2674
2674
|
],
|
|
2675
|
-
{ 'x': { 'y': 'ghi' } }
|
|
2675
|
+
lSql.json({ 'x': { 'y': 'ghi' } })
|
|
2676
2676
|
],
|
|
2677
2677
|
[
|
|
2678
2678
|
'POINT B', ['ST_POINTFROMTEXT(?)', ['POINT(123.147775 30.625016)']], { 'x': 1, 'y': 1 }, null, null
|
|
@@ -2687,7 +2687,7 @@ Result:<pre id="result">Nothing.</pre>`);
|
|
|
2687
2687
|
{ 'x': 3, 'y': 3 },
|
|
2688
2688
|
{ 'x': 1, 'y': 1 }
|
|
2689
2689
|
],
|
|
2690
|
-
{ 'x': { 'y': 'ghi' } }
|
|
2690
|
+
lSql.json({ 'x': { 'y': 'ghi' } })
|
|
2691
2691
|
],
|
|
2692
2692
|
[
|
|
2693
2693
|
'POINT B', ['ST_POINTFROMTEXT(?)', ['POINT(123.147775 30.625016)']], { 'x': 1, 'y': 1 }, null, null
|
|
@@ -2794,10 +2794,22 @@ Result:<pre id="result">Nothing.</pre>`);
|
|
|
2794
2794
|
<b>getSql() :</b> ${s}<br>
|
|
2795
2795
|
<b>getData():</b> <pre>${JSON.stringify(sd, undefined, 4)}</pre>
|
|
2796
2796
|
<b>format() :</b> ${sql.format(s, sd)}`);
|
|
2797
|
-
// --- json ---
|
|
2798
|
-
s = sql.update('json', {
|
|
2797
|
+
// --- JSON/jsonb 字段使用 json() 显式标记 ---
|
|
2798
|
+
s = sql.update('json', {
|
|
2799
|
+
'json1': lSql.json({ 'key': 'val', 'key2': 'val2' }),
|
|
2800
|
+
'json2': lSql.json([{ 'k1': 'v1' }, { 'k2': 'v2' }]),
|
|
2801
|
+
'json3': lSql.json({ 'x': 1, 'y': 2 }),
|
|
2802
|
+
'json4': lSql.json([]),
|
|
2803
|
+
'json5': lSql.json({})
|
|
2804
|
+
}).where({ 'id': 1 }).getSql();
|
|
2799
2805
|
sd = sql.getData();
|
|
2800
|
-
echo.push(`<pre>sql.update('json', {
|
|
2806
|
+
echo.push(`<pre>sql.update('json', {
|
|
2807
|
+
'json1': lSql.json({ 'key': 'val', 'key2': 'val2' }),
|
|
2808
|
+
'json2': lSql.json([ { 'k1': 'v1' }, { 'k2': 'v2' } ]),
|
|
2809
|
+
'json3': lSql.json({ 'x': 1, 'y': 2 }),
|
|
2810
|
+
'json4': lSql.json([]),
|
|
2811
|
+
'json5': lSql.json({})
|
|
2812
|
+
}).where({ 'id': 1 });</pre>
|
|
2801
2813
|
<b>getSql() :</b> ${s}<br>
|
|
2802
2814
|
<b>getData():</b> <pre>${JSON.stringify(sd, undefined, 4)}</pre>
|
|
2803
2815
|
<b>format() :</b> ${sql.format(s, sd)}`);
|