jsql-neo 5.4.2 → 5.5.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/LICENSE +202 -202
- package/README.md +8097 -7971
- package/bin/jsql +320 -320
- package/bin/jsql-server +90 -90
- package/bin/jsql-server-control.js +177 -177
- package/index.d.ts +385 -357
- package/index.js +124 -118
- package/lib/ast.js +214 -0
- package/lib/btree.js +413 -413
- package/lib/cache.js +328 -328
- package/lib/client.js +125 -125
- package/lib/database.js +1744 -1744
- package/lib/date-types.js +182 -182
- package/lib/errors.js +89 -89
- package/lib/jsql_format.js +497 -497
- package/lib/migrate.js +254 -254
- package/lib/mod.js +292 -292
- package/lib/mongo_server.js +607 -607
- package/lib/multiserver.js +140 -140
- package/lib/mysql_compat.js +413 -413
- package/lib/mysql_server.js +1237 -1237
- package/lib/native_client.js +732 -732
- package/lib/nedb_compat.js +506 -506
- package/lib/pg_server.js +864 -864
- package/lib/plugin.js +34 -34
- package/lib/query.js +815 -815
- package/lib/redis_server.js +571 -571
- package/lib/sql.js +3460 -2925
- package/lib/sqlite_compat.js +323 -323
- package/lib/sqlite_worker.js +264 -264
- package/lib/table.js +1304 -1304
- package/lib/tui.js +502 -502
- package/lib/wasm.js +1 -1
- package/lib/wasm_client.js +718 -718
- package/lib/web_ui.js +257 -257
- package/native/jsql-neo-native.node +0 -0
- package/nativesrc/jsql-neo-core/Cargo.lock +455 -455
- package/nativesrc/jsql-neo-core/Cargo.toml +24 -24
- package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +455 -455
- package/nativesrc/jsql-neo-core/src/engine/memory.rs +168 -168
- package/nativesrc/jsql-neo-core/src/engine/mod.rs +62 -62
- package/nativesrc/jsql-neo-core/src/engine/table.rs +700 -700
- package/nativesrc/jsql-neo-core/src/lib.rs +2 -2
- package/nativesrc/jsql-neo-core/src/storage/mod.rs +2 -2
- package/nativesrc/jsql-neo-core/src/storage/persistent.rs +1 -1
- package/nativesrc/jsql-neo-core/src/storage/wal.rs +85 -85
- package/nativesrc/jsql-neo-core/src/types.rs +93 -93
- package/nativesrc/jsql-neo-native/Cargo.lock +606 -606
- package/nativesrc/jsql-neo-native/Cargo.toml +16 -16
- package/nativesrc/jsql-neo-native/build.rs +3 -3
- package/nativesrc/jsql-neo-native/package.json +7 -7
- package/nativesrc/jsql-neo-native/src/lib.rs +269 -269
- package/nativesrc/jsql-neo-wasm/Cargo.lock +465 -465
- package/nativesrc/jsql-neo-wasm/Cargo.toml +15 -15
- package/nativesrc/jsql-neo-wasm/src/lib.rs +447 -447
- package/package.json +87 -86
- package/postinstall.js +158 -158
- package/test/btree.test.js +128 -128
- package/test/coverage.js +187 -187
- package/test/join.test.js +110 -110
- package/test/native.test.js +158 -158
- package/test/regress-5.1.0.js +271 -271
- package/test/smoke.js +58 -58
- package/test/sql-parser.test.js +273 -0
- package/test/wasm.test.js +110 -110
- package/wasm/browser.d.ts +89 -89
- package/wasm/browser.mjs +569 -569
- package/wasm/browser_bg.mjs +597 -597
- package/wasm/jsql_neo_wasm.d.ts +38 -38
- package/wasm/jsql_neo_wasm.js +520 -520
- package/wasm/jsql_neo_wasm_bg.wasm.d.ts +26 -26
- package/wasm/package.json +11 -11
package/lib/wasm_client.js
CHANGED
|
@@ -1,718 +1,718 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const { executeSQL } = require('./sql');
|
|
3
|
-
const wasmBindings = require(path.join(__dirname, '..', 'wasm', 'jsql_neo_wasm.js'));
|
|
4
|
-
|
|
5
|
-
function safeJsonParse(str) {
|
|
6
|
-
try { return JSON.parse(str); } catch { return str; }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const NATIVE_TYPE_MAP = {
|
|
10
|
-
text: 'string',
|
|
11
|
-
varchar: 'string',
|
|
12
|
-
double: 'float',
|
|
13
|
-
number: 'float',
|
|
14
|
-
numeric: 'float',
|
|
15
|
-
decimal: 'float',
|
|
16
|
-
date: 'string',
|
|
17
|
-
timestamp: 'string',
|
|
18
|
-
datetime: 'string',
|
|
19
|
-
json: 'string',
|
|
20
|
-
array: 'string',
|
|
21
|
-
object: 'string',
|
|
22
|
-
bool: 'boolean',
|
|
23
|
-
bigint: 'string',
|
|
24
|
-
int: 'integer',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
function parseShorthandSchema(str) {
|
|
28
|
-
const def = { type: str };
|
|
29
|
-
const strip = (re) => {
|
|
30
|
-
const next = def.type.replace(re, '').trim();
|
|
31
|
-
def.type = next || def.type;
|
|
32
|
-
};
|
|
33
|
-
if (/\bprimary\s+key\b/i.test(def.type)) { def.primaryKey = true; strip(/\bprimary\s+key\b/gi); }
|
|
34
|
-
if (/\bauto_?increment\b/i.test(def.type)) { def.autoIncrement = true; strip(/\bauto_?increment\b/gi); }
|
|
35
|
-
if (/\bnot\s+null\b/i.test(def.type)) { def.required = true; strip(/\bnot\s+null\b/gi); }
|
|
36
|
-
if (/\bunique\b/i.test(def.type)) { def.unique = true; strip(/\bunique\b/gi); }
|
|
37
|
-
if (/\bdefault\s+(\S+)/i.test(def.type)) {
|
|
38
|
-
const m = def.type.match(/\bdefault\s+(\S+)/i);
|
|
39
|
-
def.default = m[1].replace(/^['"]|['"]$/g, '');
|
|
40
|
-
strip(/\bdefault\s+\S+/gi);
|
|
41
|
-
}
|
|
42
|
-
return def;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function parseSchemaString(str) {
|
|
46
|
-
const out = {};
|
|
47
|
-
for (const seg of str.split(',')) {
|
|
48
|
-
const trimmed = seg.trim();
|
|
49
|
-
if (!trimmed) continue;
|
|
50
|
-
const parts = trimmed.split(/\s+/);
|
|
51
|
-
const name = parts.shift();
|
|
52
|
-
const def = parseShorthandSchema(parts.join(' ').toLowerCase().trim());
|
|
53
|
-
if (def.type.length === 0) def.type = 'string';
|
|
54
|
-
out[name] = def;
|
|
55
|
-
}
|
|
56
|
-
return out;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function mapNativeSchema(schema) {
|
|
60
|
-
if (typeof schema === 'string') schema = parseSchemaString(schema);
|
|
61
|
-
const mapped = {};
|
|
62
|
-
for (const [field, def] of Object.entries(schema || {})) {
|
|
63
|
-
if (typeof def === 'string') {
|
|
64
|
-
const parsed = parseShorthandSchema(def.toLowerCase().trim());
|
|
65
|
-
if (parsed.type.length === 0) parsed.type = 'string';
|
|
66
|
-
mapped[field] = { ...parsed, type: NATIVE_TYPE_MAP[parsed.type] || parsed.type };
|
|
67
|
-
} else if (def && typeof def === 'object') {
|
|
68
|
-
const t = (def.type || 'string').toLowerCase();
|
|
69
|
-
const out = { ...def, type: NATIVE_TYPE_MAP[t] || t };
|
|
70
|
-
if (out.primary_key !== undefined && out.primaryKey === undefined) { out.primaryKey = out.primary_key; delete out.primary_key; }
|
|
71
|
-
if (out.auto_increment !== undefined && out.autoIncrement === undefined) { out.autoIncrement = out.auto_increment; delete out.auto_increment; }
|
|
72
|
-
mapped[field] = out;
|
|
73
|
-
} else {
|
|
74
|
-
mapped[field] = { type: 'string' };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return mapped;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function restoreRow(row, schema) {
|
|
81
|
-
if (typeof row !== 'object' || row === null) return row;
|
|
82
|
-
let src = row;
|
|
83
|
-
if (row.fields && typeof row.fields === 'object' && row.fields !== null) {
|
|
84
|
-
src = { ...row.fields };
|
|
85
|
-
if (row.id !== undefined) {
|
|
86
|
-
const pkCols = [];
|
|
87
|
-
if (schema) {
|
|
88
|
-
for (const [k, def] of Object.entries(schema)) {
|
|
89
|
-
if (def && typeof def === 'object' && def.autoIncrement) continue;
|
|
90
|
-
const isPk = typeof def === 'string'
|
|
91
|
-
? /\bprimary\s+key\b/i.test(def)
|
|
92
|
-
: !!(def && (def.primaryKey || def.primary_key));
|
|
93
|
-
if (isPk) pkCols.push(k);
|
|
94
|
-
}
|
|
95
|
-
} else if (src.id === undefined) {
|
|
96
|
-
pkCols.push('id');
|
|
97
|
-
}
|
|
98
|
-
for (const c of pkCols) {
|
|
99
|
-
const v = src[c];
|
|
100
|
-
if (v === undefined || v === null || v === 0 || v === '' || v === false) src[c] = row.id;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (row.created_at !== undefined) src.created_at = row.created_at;
|
|
104
|
-
if (row.updated_at !== undefined) src.updated_at = row.updated_at;
|
|
105
|
-
}
|
|
106
|
-
if (!schema) return src;
|
|
107
|
-
const out = {};
|
|
108
|
-
for (const [field, value] of Object.entries(src)) {
|
|
109
|
-
const colDef = schema[field];
|
|
110
|
-
let t = null;
|
|
111
|
-
if (typeof colDef === 'string') t = colDef.toLowerCase();
|
|
112
|
-
else if (colDef && typeof colDef === 'object' && colDef.type) t = String(colDef.type).toLowerCase();
|
|
113
|
-
if (['json', 'array', 'object'].includes(t) && typeof value === 'string') {
|
|
114
|
-
try { out[field] = JSON.parse(value); } catch (e) { out[field] = value; }
|
|
115
|
-
} else if ((t === 'integer' || t === 'int' || t === 'bigint') && typeof value === 'string' && /^-?\d+$/.test(value)) {
|
|
116
|
-
out[field] = Number(value);
|
|
117
|
-
} else if ((t === 'float' || t === 'double' || t === 'number') && typeof value === 'string' && !Number.isNaN(Number(value))) {
|
|
118
|
-
out[field] = Number(value);
|
|
119
|
-
} else {
|
|
120
|
-
out[field] = value;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return out;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const INT64_TAG = 1;
|
|
127
|
-
const FLOAT_TAG = 2;
|
|
128
|
-
const STR_TAG = 3;
|
|
129
|
-
const BOOL_TAG = 4;
|
|
130
|
-
const INT32_TAG = 5;
|
|
131
|
-
|
|
132
|
-
function encodeBatch(rows) {
|
|
133
|
-
if (rows.length === 0) return new Uint8Array(0);
|
|
134
|
-
const fieldNames = Object.keys(rows[0]);
|
|
135
|
-
const nFields = fieldNames.length;
|
|
136
|
-
|
|
137
|
-
// Estimate buffer: header ~100 + rows * 120 bytes/row
|
|
138
|
-
const est = 100 + rows.length * 120;
|
|
139
|
-
const buf = Buffer.allocUnsafe(est);
|
|
140
|
-
let off = 0;
|
|
141
|
-
|
|
142
|
-
off = buf.writeUInt8(nFields, off);
|
|
143
|
-
for (let fi = 0; fi < nFields; fi++) {
|
|
144
|
-
const s = fieldNames[fi];
|
|
145
|
-
const nlen = Buffer.byteLength(s, 'utf8');
|
|
146
|
-
off = buf.writeUInt8(nlen, off);
|
|
147
|
-
off += buf.write(s, off, nlen, 'utf8');
|
|
148
|
-
}
|
|
149
|
-
off = buf.writeUInt32LE(rows.length, off);
|
|
150
|
-
|
|
151
|
-
for (let ri = 0; ri < rows.length; ri++) {
|
|
152
|
-
const row = rows[ri];
|
|
153
|
-
let fi = 0;
|
|
154
|
-
if (nFields >= 1) {
|
|
155
|
-
const v = row[fieldNames[0]];
|
|
156
|
-
if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
|
|
157
|
-
else if (typeof v === 'number') {
|
|
158
|
-
if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
|
|
159
|
-
else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
|
|
160
|
-
} else if (typeof v === 'string') {
|
|
161
|
-
off = buf.writeUInt8(STR_TAG, off);
|
|
162
|
-
const sl = Buffer.byteLength(v, 'utf8');
|
|
163
|
-
off = buf.writeUInt32LE(sl, off);
|
|
164
|
-
off += buf.write(v, off, sl, 'utf8');
|
|
165
|
-
} else if (typeof v === 'boolean') {
|
|
166
|
-
off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
|
|
167
|
-
} else {
|
|
168
|
-
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
169
|
-
off = buf.writeUInt8(STR_TAG, off);
|
|
170
|
-
const sl = Buffer.byteLength(s, 'utf8');
|
|
171
|
-
off = buf.writeUInt32LE(sl, off);
|
|
172
|
-
off += buf.write(s, off, sl, 'utf8');
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (nFields >= 2) {
|
|
176
|
-
const v = row[fieldNames[1]];
|
|
177
|
-
if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
|
|
178
|
-
else if (typeof v === 'number') {
|
|
179
|
-
if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
|
|
180
|
-
else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
|
|
181
|
-
} else if (typeof v === 'string') {
|
|
182
|
-
off = buf.writeUInt8(STR_TAG, off);
|
|
183
|
-
const sl = Buffer.byteLength(v, 'utf8');
|
|
184
|
-
off = buf.writeUInt32LE(sl, off);
|
|
185
|
-
off += buf.write(v, off, sl, 'utf8');
|
|
186
|
-
} else if (typeof v === 'boolean') {
|
|
187
|
-
off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
|
|
188
|
-
} else {
|
|
189
|
-
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
190
|
-
off = buf.writeUInt8(STR_TAG, off);
|
|
191
|
-
const sl = Buffer.byteLength(s, 'utf8');
|
|
192
|
-
off = buf.writeUInt32LE(sl, off);
|
|
193
|
-
off += buf.write(s, off, sl, 'utf8');
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (nFields >= 3) {
|
|
197
|
-
const v = row[fieldNames[2]];
|
|
198
|
-
if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
|
|
199
|
-
else if (typeof v === 'number') {
|
|
200
|
-
if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
|
|
201
|
-
else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
|
|
202
|
-
} else if (typeof v === 'string') {
|
|
203
|
-
off = buf.writeUInt8(STR_TAG, off);
|
|
204
|
-
const sl = Buffer.byteLength(v, 'utf8');
|
|
205
|
-
off = buf.writeUInt32LE(sl, off);
|
|
206
|
-
off += buf.write(v, off, sl, 'utf8');
|
|
207
|
-
} else if (typeof v === 'boolean') {
|
|
208
|
-
off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
|
|
209
|
-
} else {
|
|
210
|
-
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
211
|
-
off = buf.writeUInt8(STR_TAG, off);
|
|
212
|
-
const sl = Buffer.byteLength(s, 'utf8');
|
|
213
|
-
off = buf.writeUInt32LE(sl, off);
|
|
214
|
-
off += buf.write(s, off, sl, 'utf8');
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (nFields >= 4) {
|
|
218
|
-
const v = row[fieldNames[3]];
|
|
219
|
-
if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
|
|
220
|
-
else if (typeof v === 'number') {
|
|
221
|
-
if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
|
|
222
|
-
else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
|
|
223
|
-
} else if (typeof v === 'string') {
|
|
224
|
-
off = buf.writeUInt8(STR_TAG, off);
|
|
225
|
-
const sl = Buffer.byteLength(v, 'utf8');
|
|
226
|
-
off = buf.writeUInt32LE(sl, off);
|
|
227
|
-
off += buf.write(v, off, sl, 'utf8');
|
|
228
|
-
} else if (typeof v === 'boolean') {
|
|
229
|
-
off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
|
|
230
|
-
} else {
|
|
231
|
-
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
232
|
-
off = buf.writeUInt8(STR_TAG, off);
|
|
233
|
-
const sl = Buffer.byteLength(s, 'utf8');
|
|
234
|
-
off = buf.writeUInt32LE(sl, off);
|
|
235
|
-
off += buf.write(s, off, sl, 'utf8');
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
if (nFields >= 5) {
|
|
239
|
-
const v = row[fieldNames[4]];
|
|
240
|
-
if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
|
|
241
|
-
else if (typeof v === 'number') {
|
|
242
|
-
if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
|
|
243
|
-
else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
|
|
244
|
-
} else if (typeof v === 'string') {
|
|
245
|
-
off = buf.writeUInt8(STR_TAG, off);
|
|
246
|
-
const sl = Buffer.byteLength(v, 'utf8');
|
|
247
|
-
off = buf.writeUInt32LE(sl, off);
|
|
248
|
-
off += buf.write(v, off, sl, 'utf8');
|
|
249
|
-
} else if (typeof v === 'boolean') {
|
|
250
|
-
off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
|
|
251
|
-
} else {
|
|
252
|
-
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
253
|
-
off = buf.writeUInt8(STR_TAG, off);
|
|
254
|
-
const sl = Buffer.byteLength(s, 'utf8');
|
|
255
|
-
off = buf.writeUInt32LE(sl, off);
|
|
256
|
-
off += buf.write(s, off, sl, 'utf8');
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
// fallback for >5 fields
|
|
260
|
-
for (let fi = 5; fi < nFields; fi++) {
|
|
261
|
-
const v = row[fieldNames[fi]];
|
|
262
|
-
if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
|
|
263
|
-
else if (typeof v === 'number') {
|
|
264
|
-
if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
|
|
265
|
-
else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
|
|
266
|
-
} else if (typeof v === 'string') {
|
|
267
|
-
off = buf.writeUInt8(STR_TAG, off);
|
|
268
|
-
const sl = Buffer.byteLength(v, 'utf8');
|
|
269
|
-
off = buf.writeUInt32LE(sl, off);
|
|
270
|
-
off += buf.write(v, off, sl, 'utf8');
|
|
271
|
-
} else if (typeof v === 'boolean') {
|
|
272
|
-
off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
|
|
273
|
-
} else {
|
|
274
|
-
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
275
|
-
off = buf.writeUInt8(STR_TAG, off);
|
|
276
|
-
const sl = Buffer.byteLength(s, 'utf8');
|
|
277
|
-
off = buf.writeUInt32LE(sl, off);
|
|
278
|
-
off += buf.write(s, off, sl, 'utf8');
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return buf.subarray(0, off);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
class JSQL {
|
|
287
|
-
constructor(opts = {}) {
|
|
288
|
-
this._flushThreshold = opts.flushThreshold || 5000;
|
|
289
|
-
this._buffer = {};
|
|
290
|
-
this._bufferSize = 0;
|
|
291
|
-
this._tableNames = new Set();
|
|
292
|
-
this._schemas = {};
|
|
293
|
-
this._txId = undefined;
|
|
294
|
-
this._autoPlugins = opts.modules !== false;
|
|
295
|
-
|
|
296
|
-
this._plugins = [];
|
|
297
|
-
this._hooks = {
|
|
298
|
-
beforeInsert: [], afterInsert: [],
|
|
299
|
-
beforeUpdate: [], afterUpdate: [],
|
|
300
|
-
beforeDelete: [], afterDelete: [],
|
|
301
|
-
beforeFind: [], afterFind: [],
|
|
302
|
-
beforeCreateTable: [], afterCreateTable: [],
|
|
303
|
-
beforeDropTable: [], afterDropTable: [],
|
|
304
|
-
beforeFlush: [], afterFlush: [],
|
|
305
|
-
beforeCount: [], afterCount: [],
|
|
306
|
-
onStart: [], onStop: []
|
|
307
|
-
};
|
|
308
|
-
this._eventListeners = [];
|
|
309
|
-
if (this._autoPlugins) {
|
|
310
|
-
const { ModuleManager } = require('./mod');
|
|
311
|
-
new ModuleManager().applyTo(this);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
use(plugin) {
|
|
316
|
-
if (typeof plugin === 'function') {
|
|
317
|
-
plugin = { install: plugin };
|
|
318
|
-
}
|
|
319
|
-
if (plugin.hooks) {
|
|
320
|
-
for (const [event, fn] of Object.entries(plugin.hooks)) {
|
|
321
|
-
if (this._hooks[event]) {
|
|
322
|
-
this._hooks[event].push(fn);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
if (typeof plugin.install === 'function') {
|
|
327
|
-
plugin.install(this, this._buildCtx(plugin));
|
|
328
|
-
}
|
|
329
|
-
if (typeof plugin.onEvent === 'function') {
|
|
330
|
-
this._eventListeners.push(plugin.onEvent);
|
|
331
|
-
}
|
|
332
|
-
this._plugins.push(plugin);
|
|
333
|
-
return this;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
_buildCtx(plugin) {
|
|
337
|
-
const self = this;
|
|
338
|
-
return {
|
|
339
|
-
name: plugin.name || 'anonymous',
|
|
340
|
-
engine: this,
|
|
341
|
-
plugin,
|
|
342
|
-
on(hook, fn) { return self.on(hook, fn); },
|
|
343
|
-
onEvent(fn) { return self.onEvent(fn); },
|
|
344
|
-
emit(eventName, data) { self._emit(eventName, data); },
|
|
345
|
-
tables() { return Array.from(self._tableNames); },
|
|
346
|
-
hasTable(name) { return self._tableNames.has(name); },
|
|
347
|
-
getTableSchema(name) { return self._schemas[name] || null; }
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
on(event, fn) {
|
|
352
|
-
if (this._hooks[event]) {
|
|
353
|
-
this._hooks[event].push(fn);
|
|
354
|
-
}
|
|
355
|
-
return this;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
onEvent(fn) {
|
|
359
|
-
this._eventListeners.push(fn);
|
|
360
|
-
return this;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
_emit(eventName, data) {
|
|
364
|
-
for (const fn of this._eventListeners) {
|
|
365
|
-
try { fn(eventName, data); } catch (e) { /* ignore */ }
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
_runHooks(hookName, args) {
|
|
370
|
-
const hooks = this._hooks[hookName];
|
|
371
|
-
if (!hooks || hooks.length === 0) return true;
|
|
372
|
-
for (const fn of hooks) {
|
|
373
|
-
const r = fn(...args);
|
|
374
|
-
if (r === false) return false;
|
|
375
|
-
if (r !== undefined && args.length > 0) {
|
|
376
|
-
args[0] = r;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async start() {
|
|
383
|
-
wasmBindings.jsql_reset();
|
|
384
|
-
this._runHooks('onStart', []);
|
|
385
|
-
this._emit('start', {});
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
_pkOf(table) {
|
|
389
|
-
const schema = this._schemas[table];
|
|
390
|
-
if (!schema) return null;
|
|
391
|
-
for (const [k, def] of Object.entries(schema)) {
|
|
392
|
-
const isPk = (typeof def === 'string' && /\bprimary\s+key\b/i.test(def))
|
|
393
|
-
|| !!(def && typeof def === 'object' && (def.primaryKey || def.primary_key));
|
|
394
|
-
if (isPk) return k;
|
|
395
|
-
}
|
|
396
|
-
return null;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
_pkAutoIncrement(table) {
|
|
400
|
-
const schema = this._schemas[table];
|
|
401
|
-
const pk = this._pkOf(table);
|
|
402
|
-
if (!pk || !schema) return false;
|
|
403
|
-
const def = schema[pk];
|
|
404
|
-
if (typeof def === 'string') return /\bauto_?increment\b/i.test(def);
|
|
405
|
-
return !!(def && def.autoIncrement);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
_coercePkId(table, id) {
|
|
409
|
-
const schema = this._schemas[table];
|
|
410
|
-
const pk = this._pkOf(table);
|
|
411
|
-
if (!schema || !pk) return id;
|
|
412
|
-
const def = schema[pk];
|
|
413
|
-
let t = null;
|
|
414
|
-
if (typeof def === 'string') t = def.toLowerCase();
|
|
415
|
-
else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
|
|
416
|
-
if (['integer', 'int', 'bigint'].includes(t)) return Number(id);
|
|
417
|
-
return id;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
_findRawById(table, id) {
|
|
421
|
-
const pk = this._pkOf(table);
|
|
422
|
-
if (pk) {
|
|
423
|
-
const filter = {};
|
|
424
|
-
filter[pk] = this._coercePkId(table, id);
|
|
425
|
-
const r = safeJsonParse(wasmBindings.jsql_find(table, JSON.stringify(filter), 1, 0));
|
|
426
|
-
if (r && r.error) return r;
|
|
427
|
-
return Array.isArray(r) && r.length > 0 ? r[0] : null;
|
|
428
|
-
}
|
|
429
|
-
return safeJsonParse(wasmBindings.jsql_find_by_id(table, BigInt(id)));
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
_seqIdByPk(table, id) {
|
|
433
|
-
const pk = this._pkOf(table);
|
|
434
|
-
if (pk) {
|
|
435
|
-
const filter = {};
|
|
436
|
-
filter[pk] = this._coercePkId(table, id);
|
|
437
|
-
const r = safeJsonParse(wasmBindings.jsql_find(table, JSON.stringify(filter), 1, 0));
|
|
438
|
-
if (Array.isArray(r) && r.length > 0) return r[0].id;
|
|
439
|
-
return null;
|
|
440
|
-
}
|
|
441
|
-
return Number(id);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
async _insertBatch(table, rows) {
|
|
445
|
-
const schema = this._schemas[table];
|
|
446
|
-
if (schema) {
|
|
447
|
-
rows = rows.map(row => {
|
|
448
|
-
const out = {};
|
|
449
|
-
for (const [k, v] of Object.entries(row)) {
|
|
450
|
-
const def = schema[k];
|
|
451
|
-
let t = null;
|
|
452
|
-
if (typeof def === 'string') t = def.toLowerCase();
|
|
453
|
-
else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
|
|
454
|
-
if (v !== null && v !== undefined && ['json', 'array', 'object'].includes(t) && typeof v !== 'string') {
|
|
455
|
-
out[k] = JSON.stringify(v);
|
|
456
|
-
} else if (v instanceof Date) {
|
|
457
|
-
out[k] = v.toISOString();
|
|
458
|
-
} else {
|
|
459
|
-
out[k] = v;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
return out;
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
const r = safeJsonParse(wasmBindings.jsql_insert_json(table, JSON.stringify(rows)));
|
|
466
|
-
if (r && r.error) throw new Error(r.error);
|
|
467
|
-
if (r && Array.isArray(r) && !this._pkAutoIncrement(table)) {
|
|
468
|
-
const pk = this._pkOf(table);
|
|
469
|
-
if (pk) {
|
|
470
|
-
return r.map((seq, i) => {
|
|
471
|
-
const row = rows[i];
|
|
472
|
-
return (row && row[pk] !== undefined && row[pk] !== null) ? row[pk] : seq;
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
return r;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
async _flush() {
|
|
480
|
-
if (!this._runHooks('beforeFlush', [])) return null;
|
|
481
|
-
const flushed = {};
|
|
482
|
-
for (const [table, rows] of Object.entries(this._buffer)) {
|
|
483
|
-
if (rows.length === 0) continue;
|
|
484
|
-
const r = await this._insertBatch(table, rows);
|
|
485
|
-
flushed[table] = r;
|
|
486
|
-
}
|
|
487
|
-
this._buffer = {};
|
|
488
|
-
this._bufferSize = 0;
|
|
489
|
-
this._runHooks('afterFlush', []);
|
|
490
|
-
return flushed;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
async flush() {
|
|
494
|
-
await this._flush();
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
async insert(table, data) {
|
|
498
|
-
const arr = Array.isArray(data) ? data : [data];
|
|
499
|
-
if (!this._runHooks('beforeInsert', [table, arr])) return [];
|
|
500
|
-
if (arr.length > 1) {
|
|
501
|
-
await this._flush();
|
|
502
|
-
let result;
|
|
503
|
-
for (let i = 0; i < arr.length; i += this._flushThreshold) {
|
|
504
|
-
const chunk = arr.slice(i, i + this._flushThreshold);
|
|
505
|
-
const r = await this._insertBatch(table, chunk);
|
|
506
|
-
if (r && r.error) throw new Error(r.error);
|
|
507
|
-
if (!result) result = r;
|
|
508
|
-
}
|
|
509
|
-
this._emit('insert', { table, count: arr.length, ids: result });
|
|
510
|
-
this._runHooks('afterInsert', [table, arr, result]);
|
|
511
|
-
return result;
|
|
512
|
-
}
|
|
513
|
-
if (!this._buffer[table]) this._buffer[table] = [];
|
|
514
|
-
this._buffer[table].push(arr[0]);
|
|
515
|
-
this._bufferSize++;
|
|
516
|
-
const flushed = await this._flush();
|
|
517
|
-
return flushed && flushed[table] ? flushed[table] : null;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
async insertMany(table, data) {
|
|
521
|
-
return this.insert(table, data);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
async createTable(name, schema) {
|
|
525
|
-
await this._flush();
|
|
526
|
-
if (!this._runHooks('beforeCreateTable', [name, schema])) return null;
|
|
527
|
-
const r = safeJsonParse(wasmBindings.jsql_create_table(name, JSON.stringify(mapNativeSchema(schema))));
|
|
528
|
-
if (r && r.ok === false) throw new Error(r.error || 'create table failed');
|
|
529
|
-
this._tableNames.add(name);
|
|
530
|
-
this._schemas[name] = typeof schema === 'string' ? mapNativeSchema(schema) : schema;
|
|
531
|
-
this._emit('createTable', { name, schema });
|
|
532
|
-
this._runHooks('afterCreateTable', [name, schema]);
|
|
533
|
-
return r;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
async dropTable(name) {
|
|
537
|
-
await this._flush();
|
|
538
|
-
if (!this._runHooks('beforeDropTable', [name])) return null;
|
|
539
|
-
const r = safeJsonParse(wasmBindings.jsql_drop_table(name));
|
|
540
|
-
if (r && r.ok === false) throw new Error(r.error || 'drop table failed');
|
|
541
|
-
this._tableNames.delete(name);
|
|
542
|
-
delete this._schemas[name];
|
|
543
|
-
this._emit('dropTable', { name });
|
|
544
|
-
this._runHooks('afterDropTable', [name]);
|
|
545
|
-
return r;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
async findById(table, id) {
|
|
549
|
-
await this._flush();
|
|
550
|
-
if (!this._runHooks('beforeFind', [table, { id }])) return null;
|
|
551
|
-
const r = this._findRawById(table, id);
|
|
552
|
-
if (r && r.error) throw new Error(r.error);
|
|
553
|
-
this._runHooks('afterFind', [table, { id }, r]);
|
|
554
|
-
const schema = this._schemas[table];
|
|
555
|
-
if (schema && r && typeof r === 'object') return restoreRow(r, schema);
|
|
556
|
-
return r;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
async find(table, filter, opts = {}) {
|
|
560
|
-
await this._flush();
|
|
561
|
-
if (!this._runHooks('beforeFind', [table, { filter, opts }])) return [];
|
|
562
|
-
const filterStr = filter ? JSON.stringify(filter) : '';
|
|
563
|
-
const { limit = 100, offset = 0 } = opts;
|
|
564
|
-
const r = safeJsonParse(wasmBindings.jsql_find(table, filterStr, limit, offset));
|
|
565
|
-
if (r && r.error) throw new Error(r.error);
|
|
566
|
-
this._runHooks('afterFind', [table, { filter, opts }, r]);
|
|
567
|
-
const schema = this._schemas[table];
|
|
568
|
-
if (schema && Array.isArray(r)) return r.map(row => restoreRow(row, schema));
|
|
569
|
-
return r;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
async count(table) {
|
|
573
|
-
await this._flush();
|
|
574
|
-
this._runHooks('beforeCount', [table]);
|
|
575
|
-
const r = parseInt(wasmBindings.jsql_count(table), 10);
|
|
576
|
-
const n = isNaN(r) ? 0 : r;
|
|
577
|
-
this._runHooks('afterCount', [table, n]);
|
|
578
|
-
return n;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
async updateByIds(table, entries) {
|
|
582
|
-
const pairs = [];
|
|
583
|
-
for (const [id, data] of entries) {
|
|
584
|
-
const seq = this._seqIdByPk(table, id);
|
|
585
|
-
if (seq !== null) pairs.push([seq, data]);
|
|
586
|
-
}
|
|
587
|
-
if (!this._runHooks('beforeUpdate', [table, pairs])) return;
|
|
588
|
-
await this._flush();
|
|
589
|
-
const r = safeJsonParse(wasmBindings.jsql_update_by_ids(table, JSON.stringify(pairs)));
|
|
590
|
-
if (r && r.error) throw new Error(r.error);
|
|
591
|
-
this._emit('update', { table, entries: pairs, result: r });
|
|
592
|
-
this._runHooks('afterUpdate', [table, pairs, r]);
|
|
593
|
-
return r;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
async removeByIds(table, ids) {
|
|
597
|
-
if (!this._runHooks('beforeDelete', [table, ids])) return;
|
|
598
|
-
await this._flush();
|
|
599
|
-
const seqs = ids.map(id => this._seqIdByPk(table, id)).filter(s => s !== null);
|
|
600
|
-
const r = safeJsonParse(wasmBindings.jsql_remove_by_ids(table, JSON.stringify(seqs)));
|
|
601
|
-
if (r && r.error) throw new Error(r.error);
|
|
602
|
-
this._emit('delete', { table, ids: seqs, result: r });
|
|
603
|
-
this._runHooks('afterDelete', [table, ids, r]);
|
|
604
|
-
return r;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
async findByIds(table, ids) {
|
|
608
|
-
await this._flush();
|
|
609
|
-
if (!this._runHooks('beforeFind', [table, { ids }])) return null;
|
|
610
|
-
let r;
|
|
611
|
-
if (this._pkOf(table)) {
|
|
612
|
-
r = ids.map(id => this._findRawById(table, id)).filter(x => x !== null && !(x && x.error));
|
|
613
|
-
} else {
|
|
614
|
-
r = safeJsonParse(wasmBindings.jsql_find_by_ids(table, JSON.stringify(ids)));
|
|
615
|
-
}
|
|
616
|
-
if (r && r.error) throw new Error(r.error);
|
|
617
|
-
this._runHooks('afterFind', [table, { ids }, r]);
|
|
618
|
-
const schema = this._schemas[table];
|
|
619
|
-
if (schema && Array.isArray(r)) return r.map(row => restoreRow(row, schema));
|
|
620
|
-
return r;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
findByIdsRaw(table, ids) {
|
|
624
|
-
return this.findByIds(table, ids);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
async updateById(table, id, data) {
|
|
628
|
-
if (!this._runHooks('beforeUpdate', [table, id, data])) return;
|
|
629
|
-
await this._flush();
|
|
630
|
-
const seq = this._seqIdByPk(table, id);
|
|
631
|
-
if (seq === null) return null;
|
|
632
|
-
const r = safeJsonParse(wasmBindings.jsql_update_by_id(table, BigInt(seq), JSON.stringify(data)));
|
|
633
|
-
if (r && r.ok === false) throw new Error(r.error || 'update failed');
|
|
634
|
-
this._emit('update', { table, id, data });
|
|
635
|
-
this._runHooks('afterUpdate', [table, id, data, r]);
|
|
636
|
-
return r;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
async removeById(table, id) {
|
|
640
|
-
if (!this._runHooks('beforeDelete', [table, id])) return;
|
|
641
|
-
await this._flush();
|
|
642
|
-
const seq = this._seqIdByPk(table, id);
|
|
643
|
-
if (seq === null) return null;
|
|
644
|
-
const r = safeJsonParse(wasmBindings.jsql_remove_by_id(table, BigInt(seq)));
|
|
645
|
-
if (r && r.ok === false) throw new Error(r.error || 'remove failed');
|
|
646
|
-
this._emit('delete', { table, id });
|
|
647
|
-
this._runHooks('afterDelete', [table, id, r]);
|
|
648
|
-
return r;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
async beginTx() {
|
|
652
|
-
await this._flush();
|
|
653
|
-
const r = safeJsonParse(wasmBindings.jsql_begin_tx());
|
|
654
|
-
if (r && r.ok === false) throw new Error(r.error || 'begin transaction failed');
|
|
655
|
-
this._txId = r.txId;
|
|
656
|
-
return r.txId;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
async commitTx(txId) {
|
|
660
|
-
const r = safeJsonParse(wasmBindings.jsql_commit_tx(String(txId)));
|
|
661
|
-
if (r && r.ok === false) throw new Error(r.error || 'commit transaction failed');
|
|
662
|
-
return true;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
async rollbackTx(txId) {
|
|
666
|
-
const r = safeJsonParse(wasmBindings.jsql_rollback_tx(String(txId)));
|
|
667
|
-
if (r && r.ok === false) throw new Error(r.error || 'rollback transaction failed');
|
|
668
|
-
return true;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
async beginTransaction() {
|
|
672
|
-
return this.beginTx();
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
async commit() {
|
|
676
|
-
if (this._txId === undefined) return true;
|
|
677
|
-
const r = await this.commitTx(this._txId);
|
|
678
|
-
this._txId = undefined;
|
|
679
|
-
return r;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
async rollback() {
|
|
683
|
-
if (this._txId === undefined) return true;
|
|
684
|
-
const r = await this.rollbackTx(this._txId);
|
|
685
|
-
this._txId = undefined;
|
|
686
|
-
return r;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
async hasTable(name) {
|
|
690
|
-
return this._tableNames.has(name);
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
async getTables() {
|
|
694
|
-
return Array.from(this._tableNames);
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
async getTableSchema(name) {
|
|
698
|
-
return this._schemas[name] || null;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
/**
|
|
702
|
-
* 直接执行原生 SQL 语句。本实例自身即充当 engine,
|
|
703
|
-
* 支持 SELECT / INSERT / UPDATE / DELETE / CREATE TABLE 等常用语句。
|
|
704
|
-
* @example
|
|
705
|
-
* const rows = await db.query("SELECT * FROM users WHERE age > ?", [21]);
|
|
706
|
-
*/
|
|
707
|
-
async query(sql, paramsOrOpts, opts) {
|
|
708
|
-
return executeSQL(this, sql, paramsOrOpts, opts);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
async stop() {
|
|
712
|
-
await this._flush();
|
|
713
|
-
this._runHooks('onStop', []);
|
|
714
|
-
this._emit('stop', {});
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
module.exports = { JSQL };
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { executeSQL } = require('./sql');
|
|
3
|
+
const wasmBindings = require(path.join(__dirname, '..', 'wasm', 'jsql_neo_wasm.js'));
|
|
4
|
+
|
|
5
|
+
function safeJsonParse(str) {
|
|
6
|
+
try { return JSON.parse(str); } catch { return str; }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const NATIVE_TYPE_MAP = {
|
|
10
|
+
text: 'string',
|
|
11
|
+
varchar: 'string',
|
|
12
|
+
double: 'float',
|
|
13
|
+
number: 'float',
|
|
14
|
+
numeric: 'float',
|
|
15
|
+
decimal: 'float',
|
|
16
|
+
date: 'string',
|
|
17
|
+
timestamp: 'string',
|
|
18
|
+
datetime: 'string',
|
|
19
|
+
json: 'string',
|
|
20
|
+
array: 'string',
|
|
21
|
+
object: 'string',
|
|
22
|
+
bool: 'boolean',
|
|
23
|
+
bigint: 'string',
|
|
24
|
+
int: 'integer',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function parseShorthandSchema(str) {
|
|
28
|
+
const def = { type: str };
|
|
29
|
+
const strip = (re) => {
|
|
30
|
+
const next = def.type.replace(re, '').trim();
|
|
31
|
+
def.type = next || def.type;
|
|
32
|
+
};
|
|
33
|
+
if (/\bprimary\s+key\b/i.test(def.type)) { def.primaryKey = true; strip(/\bprimary\s+key\b/gi); }
|
|
34
|
+
if (/\bauto_?increment\b/i.test(def.type)) { def.autoIncrement = true; strip(/\bauto_?increment\b/gi); }
|
|
35
|
+
if (/\bnot\s+null\b/i.test(def.type)) { def.required = true; strip(/\bnot\s+null\b/gi); }
|
|
36
|
+
if (/\bunique\b/i.test(def.type)) { def.unique = true; strip(/\bunique\b/gi); }
|
|
37
|
+
if (/\bdefault\s+(\S+)/i.test(def.type)) {
|
|
38
|
+
const m = def.type.match(/\bdefault\s+(\S+)/i);
|
|
39
|
+
def.default = m[1].replace(/^['"]|['"]$/g, '');
|
|
40
|
+
strip(/\bdefault\s+\S+/gi);
|
|
41
|
+
}
|
|
42
|
+
return def;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseSchemaString(str) {
|
|
46
|
+
const out = {};
|
|
47
|
+
for (const seg of str.split(',')) {
|
|
48
|
+
const trimmed = seg.trim();
|
|
49
|
+
if (!trimmed) continue;
|
|
50
|
+
const parts = trimmed.split(/\s+/);
|
|
51
|
+
const name = parts.shift();
|
|
52
|
+
const def = parseShorthandSchema(parts.join(' ').toLowerCase().trim());
|
|
53
|
+
if (def.type.length === 0) def.type = 'string';
|
|
54
|
+
out[name] = def;
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function mapNativeSchema(schema) {
|
|
60
|
+
if (typeof schema === 'string') schema = parseSchemaString(schema);
|
|
61
|
+
const mapped = {};
|
|
62
|
+
for (const [field, def] of Object.entries(schema || {})) {
|
|
63
|
+
if (typeof def === 'string') {
|
|
64
|
+
const parsed = parseShorthandSchema(def.toLowerCase().trim());
|
|
65
|
+
if (parsed.type.length === 0) parsed.type = 'string';
|
|
66
|
+
mapped[field] = { ...parsed, type: NATIVE_TYPE_MAP[parsed.type] || parsed.type };
|
|
67
|
+
} else if (def && typeof def === 'object') {
|
|
68
|
+
const t = (def.type || 'string').toLowerCase();
|
|
69
|
+
const out = { ...def, type: NATIVE_TYPE_MAP[t] || t };
|
|
70
|
+
if (out.primary_key !== undefined && out.primaryKey === undefined) { out.primaryKey = out.primary_key; delete out.primary_key; }
|
|
71
|
+
if (out.auto_increment !== undefined && out.autoIncrement === undefined) { out.autoIncrement = out.auto_increment; delete out.auto_increment; }
|
|
72
|
+
mapped[field] = out;
|
|
73
|
+
} else {
|
|
74
|
+
mapped[field] = { type: 'string' };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return mapped;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function restoreRow(row, schema) {
|
|
81
|
+
if (typeof row !== 'object' || row === null) return row;
|
|
82
|
+
let src = row;
|
|
83
|
+
if (row.fields && typeof row.fields === 'object' && row.fields !== null) {
|
|
84
|
+
src = { ...row.fields };
|
|
85
|
+
if (row.id !== undefined) {
|
|
86
|
+
const pkCols = [];
|
|
87
|
+
if (schema) {
|
|
88
|
+
for (const [k, def] of Object.entries(schema)) {
|
|
89
|
+
if (def && typeof def === 'object' && def.autoIncrement) continue;
|
|
90
|
+
const isPk = typeof def === 'string'
|
|
91
|
+
? /\bprimary\s+key\b/i.test(def)
|
|
92
|
+
: !!(def && (def.primaryKey || def.primary_key));
|
|
93
|
+
if (isPk) pkCols.push(k);
|
|
94
|
+
}
|
|
95
|
+
} else if (src.id === undefined) {
|
|
96
|
+
pkCols.push('id');
|
|
97
|
+
}
|
|
98
|
+
for (const c of pkCols) {
|
|
99
|
+
const v = src[c];
|
|
100
|
+
if (v === undefined || v === null || v === 0 || v === '' || v === false) src[c] = row.id;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (row.created_at !== undefined) src.created_at = row.created_at;
|
|
104
|
+
if (row.updated_at !== undefined) src.updated_at = row.updated_at;
|
|
105
|
+
}
|
|
106
|
+
if (!schema) return src;
|
|
107
|
+
const out = {};
|
|
108
|
+
for (const [field, value] of Object.entries(src)) {
|
|
109
|
+
const colDef = schema[field];
|
|
110
|
+
let t = null;
|
|
111
|
+
if (typeof colDef === 'string') t = colDef.toLowerCase();
|
|
112
|
+
else if (colDef && typeof colDef === 'object' && colDef.type) t = String(colDef.type).toLowerCase();
|
|
113
|
+
if (['json', 'array', 'object'].includes(t) && typeof value === 'string') {
|
|
114
|
+
try { out[field] = JSON.parse(value); } catch (e) { out[field] = value; }
|
|
115
|
+
} else if ((t === 'integer' || t === 'int' || t === 'bigint') && typeof value === 'string' && /^-?\d+$/.test(value)) {
|
|
116
|
+
out[field] = Number(value);
|
|
117
|
+
} else if ((t === 'float' || t === 'double' || t === 'number') && typeof value === 'string' && !Number.isNaN(Number(value))) {
|
|
118
|
+
out[field] = Number(value);
|
|
119
|
+
} else {
|
|
120
|
+
out[field] = value;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const INT64_TAG = 1;
|
|
127
|
+
const FLOAT_TAG = 2;
|
|
128
|
+
const STR_TAG = 3;
|
|
129
|
+
const BOOL_TAG = 4;
|
|
130
|
+
const INT32_TAG = 5;
|
|
131
|
+
|
|
132
|
+
function encodeBatch(rows) {
|
|
133
|
+
if (rows.length === 0) return new Uint8Array(0);
|
|
134
|
+
const fieldNames = Object.keys(rows[0]);
|
|
135
|
+
const nFields = fieldNames.length;
|
|
136
|
+
|
|
137
|
+
// Estimate buffer: header ~100 + rows * 120 bytes/row
|
|
138
|
+
const est = 100 + rows.length * 120;
|
|
139
|
+
const buf = Buffer.allocUnsafe(est);
|
|
140
|
+
let off = 0;
|
|
141
|
+
|
|
142
|
+
off = buf.writeUInt8(nFields, off);
|
|
143
|
+
for (let fi = 0; fi < nFields; fi++) {
|
|
144
|
+
const s = fieldNames[fi];
|
|
145
|
+
const nlen = Buffer.byteLength(s, 'utf8');
|
|
146
|
+
off = buf.writeUInt8(nlen, off);
|
|
147
|
+
off += buf.write(s, off, nlen, 'utf8');
|
|
148
|
+
}
|
|
149
|
+
off = buf.writeUInt32LE(rows.length, off);
|
|
150
|
+
|
|
151
|
+
for (let ri = 0; ri < rows.length; ri++) {
|
|
152
|
+
const row = rows[ri];
|
|
153
|
+
let fi = 0;
|
|
154
|
+
if (nFields >= 1) {
|
|
155
|
+
const v = row[fieldNames[0]];
|
|
156
|
+
if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
|
|
157
|
+
else if (typeof v === 'number') {
|
|
158
|
+
if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
|
|
159
|
+
else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
|
|
160
|
+
} else if (typeof v === 'string') {
|
|
161
|
+
off = buf.writeUInt8(STR_TAG, off);
|
|
162
|
+
const sl = Buffer.byteLength(v, 'utf8');
|
|
163
|
+
off = buf.writeUInt32LE(sl, off);
|
|
164
|
+
off += buf.write(v, off, sl, 'utf8');
|
|
165
|
+
} else if (typeof v === 'boolean') {
|
|
166
|
+
off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
|
|
167
|
+
} else {
|
|
168
|
+
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
169
|
+
off = buf.writeUInt8(STR_TAG, off);
|
|
170
|
+
const sl = Buffer.byteLength(s, 'utf8');
|
|
171
|
+
off = buf.writeUInt32LE(sl, off);
|
|
172
|
+
off += buf.write(s, off, sl, 'utf8');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (nFields >= 2) {
|
|
176
|
+
const v = row[fieldNames[1]];
|
|
177
|
+
if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
|
|
178
|
+
else if (typeof v === 'number') {
|
|
179
|
+
if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
|
|
180
|
+
else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
|
|
181
|
+
} else if (typeof v === 'string') {
|
|
182
|
+
off = buf.writeUInt8(STR_TAG, off);
|
|
183
|
+
const sl = Buffer.byteLength(v, 'utf8');
|
|
184
|
+
off = buf.writeUInt32LE(sl, off);
|
|
185
|
+
off += buf.write(v, off, sl, 'utf8');
|
|
186
|
+
} else if (typeof v === 'boolean') {
|
|
187
|
+
off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
|
|
188
|
+
} else {
|
|
189
|
+
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
190
|
+
off = buf.writeUInt8(STR_TAG, off);
|
|
191
|
+
const sl = Buffer.byteLength(s, 'utf8');
|
|
192
|
+
off = buf.writeUInt32LE(sl, off);
|
|
193
|
+
off += buf.write(s, off, sl, 'utf8');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (nFields >= 3) {
|
|
197
|
+
const v = row[fieldNames[2]];
|
|
198
|
+
if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
|
|
199
|
+
else if (typeof v === 'number') {
|
|
200
|
+
if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
|
|
201
|
+
else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
|
|
202
|
+
} else if (typeof v === 'string') {
|
|
203
|
+
off = buf.writeUInt8(STR_TAG, off);
|
|
204
|
+
const sl = Buffer.byteLength(v, 'utf8');
|
|
205
|
+
off = buf.writeUInt32LE(sl, off);
|
|
206
|
+
off += buf.write(v, off, sl, 'utf8');
|
|
207
|
+
} else if (typeof v === 'boolean') {
|
|
208
|
+
off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
|
|
209
|
+
} else {
|
|
210
|
+
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
211
|
+
off = buf.writeUInt8(STR_TAG, off);
|
|
212
|
+
const sl = Buffer.byteLength(s, 'utf8');
|
|
213
|
+
off = buf.writeUInt32LE(sl, off);
|
|
214
|
+
off += buf.write(s, off, sl, 'utf8');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (nFields >= 4) {
|
|
218
|
+
const v = row[fieldNames[3]];
|
|
219
|
+
if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
|
|
220
|
+
else if (typeof v === 'number') {
|
|
221
|
+
if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
|
|
222
|
+
else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
|
|
223
|
+
} else if (typeof v === 'string') {
|
|
224
|
+
off = buf.writeUInt8(STR_TAG, off);
|
|
225
|
+
const sl = Buffer.byteLength(v, 'utf8');
|
|
226
|
+
off = buf.writeUInt32LE(sl, off);
|
|
227
|
+
off += buf.write(v, off, sl, 'utf8');
|
|
228
|
+
} else if (typeof v === 'boolean') {
|
|
229
|
+
off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
|
|
230
|
+
} else {
|
|
231
|
+
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
232
|
+
off = buf.writeUInt8(STR_TAG, off);
|
|
233
|
+
const sl = Buffer.byteLength(s, 'utf8');
|
|
234
|
+
off = buf.writeUInt32LE(sl, off);
|
|
235
|
+
off += buf.write(s, off, sl, 'utf8');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (nFields >= 5) {
|
|
239
|
+
const v = row[fieldNames[4]];
|
|
240
|
+
if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
|
|
241
|
+
else if (typeof v === 'number') {
|
|
242
|
+
if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
|
|
243
|
+
else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
|
|
244
|
+
} else if (typeof v === 'string') {
|
|
245
|
+
off = buf.writeUInt8(STR_TAG, off);
|
|
246
|
+
const sl = Buffer.byteLength(v, 'utf8');
|
|
247
|
+
off = buf.writeUInt32LE(sl, off);
|
|
248
|
+
off += buf.write(v, off, sl, 'utf8');
|
|
249
|
+
} else if (typeof v === 'boolean') {
|
|
250
|
+
off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
|
|
251
|
+
} else {
|
|
252
|
+
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
253
|
+
off = buf.writeUInt8(STR_TAG, off);
|
|
254
|
+
const sl = Buffer.byteLength(s, 'utf8');
|
|
255
|
+
off = buf.writeUInt32LE(sl, off);
|
|
256
|
+
off += buf.write(s, off, sl, 'utf8');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// fallback for >5 fields
|
|
260
|
+
for (let fi = 5; fi < nFields; fi++) {
|
|
261
|
+
const v = row[fieldNames[fi]];
|
|
262
|
+
if (v === null || v === undefined) { off = buf.writeUInt8(0, off); }
|
|
263
|
+
else if (typeof v === 'number') {
|
|
264
|
+
if (Number.isInteger(v)) { if (v >= -2147483648 && v <= 2147483647) { off = buf.writeUInt8(INT32_TAG, off); off = buf.writeInt32LE(v, off); } else { off = buf.writeUInt8(INT64_TAG, off); off = buf.writeBigInt64LE(BigInt(v), off); } }
|
|
265
|
+
else { off = buf.writeUInt8(FLOAT_TAG, off); off = buf.writeDoubleLE(v, off); }
|
|
266
|
+
} else if (typeof v === 'string') {
|
|
267
|
+
off = buf.writeUInt8(STR_TAG, off);
|
|
268
|
+
const sl = Buffer.byteLength(v, 'utf8');
|
|
269
|
+
off = buf.writeUInt32LE(sl, off);
|
|
270
|
+
off += buf.write(v, off, sl, 'utf8');
|
|
271
|
+
} else if (typeof v === 'boolean') {
|
|
272
|
+
off = buf.writeUInt8(BOOL_TAG, off); off = buf.writeUInt8(v ? 1 : 0, off);
|
|
273
|
+
} else {
|
|
274
|
+
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
275
|
+
off = buf.writeUInt8(STR_TAG, off);
|
|
276
|
+
const sl = Buffer.byteLength(s, 'utf8');
|
|
277
|
+
off = buf.writeUInt32LE(sl, off);
|
|
278
|
+
off += buf.write(s, off, sl, 'utf8');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return buf.subarray(0, off);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
class JSQL {
|
|
287
|
+
constructor(opts = {}) {
|
|
288
|
+
this._flushThreshold = opts.flushThreshold || 5000;
|
|
289
|
+
this._buffer = {};
|
|
290
|
+
this._bufferSize = 0;
|
|
291
|
+
this._tableNames = new Set();
|
|
292
|
+
this._schemas = {};
|
|
293
|
+
this._txId = undefined;
|
|
294
|
+
this._autoPlugins = opts.modules !== false;
|
|
295
|
+
|
|
296
|
+
this._plugins = [];
|
|
297
|
+
this._hooks = {
|
|
298
|
+
beforeInsert: [], afterInsert: [],
|
|
299
|
+
beforeUpdate: [], afterUpdate: [],
|
|
300
|
+
beforeDelete: [], afterDelete: [],
|
|
301
|
+
beforeFind: [], afterFind: [],
|
|
302
|
+
beforeCreateTable: [], afterCreateTable: [],
|
|
303
|
+
beforeDropTable: [], afterDropTable: [],
|
|
304
|
+
beforeFlush: [], afterFlush: [],
|
|
305
|
+
beforeCount: [], afterCount: [],
|
|
306
|
+
onStart: [], onStop: []
|
|
307
|
+
};
|
|
308
|
+
this._eventListeners = [];
|
|
309
|
+
if (this._autoPlugins) {
|
|
310
|
+
const { ModuleManager } = require('./mod');
|
|
311
|
+
new ModuleManager().applyTo(this);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
use(plugin) {
|
|
316
|
+
if (typeof plugin === 'function') {
|
|
317
|
+
plugin = { install: plugin };
|
|
318
|
+
}
|
|
319
|
+
if (plugin.hooks) {
|
|
320
|
+
for (const [event, fn] of Object.entries(plugin.hooks)) {
|
|
321
|
+
if (this._hooks[event]) {
|
|
322
|
+
this._hooks[event].push(fn);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (typeof plugin.install === 'function') {
|
|
327
|
+
plugin.install(this, this._buildCtx(plugin));
|
|
328
|
+
}
|
|
329
|
+
if (typeof plugin.onEvent === 'function') {
|
|
330
|
+
this._eventListeners.push(plugin.onEvent);
|
|
331
|
+
}
|
|
332
|
+
this._plugins.push(plugin);
|
|
333
|
+
return this;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
_buildCtx(plugin) {
|
|
337
|
+
const self = this;
|
|
338
|
+
return {
|
|
339
|
+
name: plugin.name || 'anonymous',
|
|
340
|
+
engine: this,
|
|
341
|
+
plugin,
|
|
342
|
+
on(hook, fn) { return self.on(hook, fn); },
|
|
343
|
+
onEvent(fn) { return self.onEvent(fn); },
|
|
344
|
+
emit(eventName, data) { self._emit(eventName, data); },
|
|
345
|
+
tables() { return Array.from(self._tableNames); },
|
|
346
|
+
hasTable(name) { return self._tableNames.has(name); },
|
|
347
|
+
getTableSchema(name) { return self._schemas[name] || null; }
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
on(event, fn) {
|
|
352
|
+
if (this._hooks[event]) {
|
|
353
|
+
this._hooks[event].push(fn);
|
|
354
|
+
}
|
|
355
|
+
return this;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
onEvent(fn) {
|
|
359
|
+
this._eventListeners.push(fn);
|
|
360
|
+
return this;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
_emit(eventName, data) {
|
|
364
|
+
for (const fn of this._eventListeners) {
|
|
365
|
+
try { fn(eventName, data); } catch (e) { /* ignore */ }
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
_runHooks(hookName, args) {
|
|
370
|
+
const hooks = this._hooks[hookName];
|
|
371
|
+
if (!hooks || hooks.length === 0) return true;
|
|
372
|
+
for (const fn of hooks) {
|
|
373
|
+
const r = fn(...args);
|
|
374
|
+
if (r === false) return false;
|
|
375
|
+
if (r !== undefined && args.length > 0) {
|
|
376
|
+
args[0] = r;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async start() {
|
|
383
|
+
wasmBindings.jsql_reset();
|
|
384
|
+
this._runHooks('onStart', []);
|
|
385
|
+
this._emit('start', {});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
_pkOf(table) {
|
|
389
|
+
const schema = this._schemas[table];
|
|
390
|
+
if (!schema) return null;
|
|
391
|
+
for (const [k, def] of Object.entries(schema)) {
|
|
392
|
+
const isPk = (typeof def === 'string' && /\bprimary\s+key\b/i.test(def))
|
|
393
|
+
|| !!(def && typeof def === 'object' && (def.primaryKey || def.primary_key));
|
|
394
|
+
if (isPk) return k;
|
|
395
|
+
}
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
_pkAutoIncrement(table) {
|
|
400
|
+
const schema = this._schemas[table];
|
|
401
|
+
const pk = this._pkOf(table);
|
|
402
|
+
if (!pk || !schema) return false;
|
|
403
|
+
const def = schema[pk];
|
|
404
|
+
if (typeof def === 'string') return /\bauto_?increment\b/i.test(def);
|
|
405
|
+
return !!(def && def.autoIncrement);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
_coercePkId(table, id) {
|
|
409
|
+
const schema = this._schemas[table];
|
|
410
|
+
const pk = this._pkOf(table);
|
|
411
|
+
if (!schema || !pk) return id;
|
|
412
|
+
const def = schema[pk];
|
|
413
|
+
let t = null;
|
|
414
|
+
if (typeof def === 'string') t = def.toLowerCase();
|
|
415
|
+
else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
|
|
416
|
+
if (['integer', 'int', 'bigint'].includes(t)) return Number(id);
|
|
417
|
+
return id;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
_findRawById(table, id) {
|
|
421
|
+
const pk = this._pkOf(table);
|
|
422
|
+
if (pk) {
|
|
423
|
+
const filter = {};
|
|
424
|
+
filter[pk] = this._coercePkId(table, id);
|
|
425
|
+
const r = safeJsonParse(wasmBindings.jsql_find(table, JSON.stringify(filter), 1, 0));
|
|
426
|
+
if (r && r.error) return r;
|
|
427
|
+
return Array.isArray(r) && r.length > 0 ? r[0] : null;
|
|
428
|
+
}
|
|
429
|
+
return safeJsonParse(wasmBindings.jsql_find_by_id(table, BigInt(id)));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
_seqIdByPk(table, id) {
|
|
433
|
+
const pk = this._pkOf(table);
|
|
434
|
+
if (pk) {
|
|
435
|
+
const filter = {};
|
|
436
|
+
filter[pk] = this._coercePkId(table, id);
|
|
437
|
+
const r = safeJsonParse(wasmBindings.jsql_find(table, JSON.stringify(filter), 1, 0));
|
|
438
|
+
if (Array.isArray(r) && r.length > 0) return r[0].id;
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
return Number(id);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async _insertBatch(table, rows) {
|
|
445
|
+
const schema = this._schemas[table];
|
|
446
|
+
if (schema) {
|
|
447
|
+
rows = rows.map(row => {
|
|
448
|
+
const out = {};
|
|
449
|
+
for (const [k, v] of Object.entries(row)) {
|
|
450
|
+
const def = schema[k];
|
|
451
|
+
let t = null;
|
|
452
|
+
if (typeof def === 'string') t = def.toLowerCase();
|
|
453
|
+
else if (def && typeof def === 'object' && def.type) t = String(def.type).toLowerCase();
|
|
454
|
+
if (v !== null && v !== undefined && ['json', 'array', 'object'].includes(t) && typeof v !== 'string') {
|
|
455
|
+
out[k] = JSON.stringify(v);
|
|
456
|
+
} else if (v instanceof Date) {
|
|
457
|
+
out[k] = v.toISOString();
|
|
458
|
+
} else {
|
|
459
|
+
out[k] = v;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return out;
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
const r = safeJsonParse(wasmBindings.jsql_insert_json(table, JSON.stringify(rows)));
|
|
466
|
+
if (r && r.error) throw new Error(r.error);
|
|
467
|
+
if (r && Array.isArray(r) && !this._pkAutoIncrement(table)) {
|
|
468
|
+
const pk = this._pkOf(table);
|
|
469
|
+
if (pk) {
|
|
470
|
+
return r.map((seq, i) => {
|
|
471
|
+
const row = rows[i];
|
|
472
|
+
return (row && row[pk] !== undefined && row[pk] !== null) ? row[pk] : seq;
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return r;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async _flush() {
|
|
480
|
+
if (!this._runHooks('beforeFlush', [])) return null;
|
|
481
|
+
const flushed = {};
|
|
482
|
+
for (const [table, rows] of Object.entries(this._buffer)) {
|
|
483
|
+
if (rows.length === 0) continue;
|
|
484
|
+
const r = await this._insertBatch(table, rows);
|
|
485
|
+
flushed[table] = r;
|
|
486
|
+
}
|
|
487
|
+
this._buffer = {};
|
|
488
|
+
this._bufferSize = 0;
|
|
489
|
+
this._runHooks('afterFlush', []);
|
|
490
|
+
return flushed;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async flush() {
|
|
494
|
+
await this._flush();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async insert(table, data) {
|
|
498
|
+
const arr = Array.isArray(data) ? data : [data];
|
|
499
|
+
if (!this._runHooks('beforeInsert', [table, arr])) return [];
|
|
500
|
+
if (arr.length > 1) {
|
|
501
|
+
await this._flush();
|
|
502
|
+
let result;
|
|
503
|
+
for (let i = 0; i < arr.length; i += this._flushThreshold) {
|
|
504
|
+
const chunk = arr.slice(i, i + this._flushThreshold);
|
|
505
|
+
const r = await this._insertBatch(table, chunk);
|
|
506
|
+
if (r && r.error) throw new Error(r.error);
|
|
507
|
+
if (!result) result = r;
|
|
508
|
+
}
|
|
509
|
+
this._emit('insert', { table, count: arr.length, ids: result });
|
|
510
|
+
this._runHooks('afterInsert', [table, arr, result]);
|
|
511
|
+
return result;
|
|
512
|
+
}
|
|
513
|
+
if (!this._buffer[table]) this._buffer[table] = [];
|
|
514
|
+
this._buffer[table].push(arr[0]);
|
|
515
|
+
this._bufferSize++;
|
|
516
|
+
const flushed = await this._flush();
|
|
517
|
+
return flushed && flushed[table] ? flushed[table] : null;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async insertMany(table, data) {
|
|
521
|
+
return this.insert(table, data);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async createTable(name, schema) {
|
|
525
|
+
await this._flush();
|
|
526
|
+
if (!this._runHooks('beforeCreateTable', [name, schema])) return null;
|
|
527
|
+
const r = safeJsonParse(wasmBindings.jsql_create_table(name, JSON.stringify(mapNativeSchema(schema))));
|
|
528
|
+
if (r && r.ok === false) throw new Error(r.error || 'create table failed');
|
|
529
|
+
this._tableNames.add(name);
|
|
530
|
+
this._schemas[name] = typeof schema === 'string' ? mapNativeSchema(schema) : schema;
|
|
531
|
+
this._emit('createTable', { name, schema });
|
|
532
|
+
this._runHooks('afterCreateTable', [name, schema]);
|
|
533
|
+
return r;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async dropTable(name) {
|
|
537
|
+
await this._flush();
|
|
538
|
+
if (!this._runHooks('beforeDropTable', [name])) return null;
|
|
539
|
+
const r = safeJsonParse(wasmBindings.jsql_drop_table(name));
|
|
540
|
+
if (r && r.ok === false) throw new Error(r.error || 'drop table failed');
|
|
541
|
+
this._tableNames.delete(name);
|
|
542
|
+
delete this._schemas[name];
|
|
543
|
+
this._emit('dropTable', { name });
|
|
544
|
+
this._runHooks('afterDropTable', [name]);
|
|
545
|
+
return r;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async findById(table, id) {
|
|
549
|
+
await this._flush();
|
|
550
|
+
if (!this._runHooks('beforeFind', [table, { id }])) return null;
|
|
551
|
+
const r = this._findRawById(table, id);
|
|
552
|
+
if (r && r.error) throw new Error(r.error);
|
|
553
|
+
this._runHooks('afterFind', [table, { id }, r]);
|
|
554
|
+
const schema = this._schemas[table];
|
|
555
|
+
if (schema && r && typeof r === 'object') return restoreRow(r, schema);
|
|
556
|
+
return r;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async find(table, filter, opts = {}) {
|
|
560
|
+
await this._flush();
|
|
561
|
+
if (!this._runHooks('beforeFind', [table, { filter, opts }])) return [];
|
|
562
|
+
const filterStr = filter ? JSON.stringify(filter) : '';
|
|
563
|
+
const { limit = 100, offset = 0 } = opts;
|
|
564
|
+
const r = safeJsonParse(wasmBindings.jsql_find(table, filterStr, limit, offset));
|
|
565
|
+
if (r && r.error) throw new Error(r.error);
|
|
566
|
+
this._runHooks('afterFind', [table, { filter, opts }, r]);
|
|
567
|
+
const schema = this._schemas[table];
|
|
568
|
+
if (schema && Array.isArray(r)) return r.map(row => restoreRow(row, schema));
|
|
569
|
+
return r;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async count(table) {
|
|
573
|
+
await this._flush();
|
|
574
|
+
this._runHooks('beforeCount', [table]);
|
|
575
|
+
const r = parseInt(wasmBindings.jsql_count(table), 10);
|
|
576
|
+
const n = isNaN(r) ? 0 : r;
|
|
577
|
+
this._runHooks('afterCount', [table, n]);
|
|
578
|
+
return n;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async updateByIds(table, entries) {
|
|
582
|
+
const pairs = [];
|
|
583
|
+
for (const [id, data] of entries) {
|
|
584
|
+
const seq = this._seqIdByPk(table, id);
|
|
585
|
+
if (seq !== null) pairs.push([seq, data]);
|
|
586
|
+
}
|
|
587
|
+
if (!this._runHooks('beforeUpdate', [table, pairs])) return;
|
|
588
|
+
await this._flush();
|
|
589
|
+
const r = safeJsonParse(wasmBindings.jsql_update_by_ids(table, JSON.stringify(pairs)));
|
|
590
|
+
if (r && r.error) throw new Error(r.error);
|
|
591
|
+
this._emit('update', { table, entries: pairs, result: r });
|
|
592
|
+
this._runHooks('afterUpdate', [table, pairs, r]);
|
|
593
|
+
return r;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async removeByIds(table, ids) {
|
|
597
|
+
if (!this._runHooks('beforeDelete', [table, ids])) return;
|
|
598
|
+
await this._flush();
|
|
599
|
+
const seqs = ids.map(id => this._seqIdByPk(table, id)).filter(s => s !== null);
|
|
600
|
+
const r = safeJsonParse(wasmBindings.jsql_remove_by_ids(table, JSON.stringify(seqs)));
|
|
601
|
+
if (r && r.error) throw new Error(r.error);
|
|
602
|
+
this._emit('delete', { table, ids: seqs, result: r });
|
|
603
|
+
this._runHooks('afterDelete', [table, ids, r]);
|
|
604
|
+
return r;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async findByIds(table, ids) {
|
|
608
|
+
await this._flush();
|
|
609
|
+
if (!this._runHooks('beforeFind', [table, { ids }])) return null;
|
|
610
|
+
let r;
|
|
611
|
+
if (this._pkOf(table)) {
|
|
612
|
+
r = ids.map(id => this._findRawById(table, id)).filter(x => x !== null && !(x && x.error));
|
|
613
|
+
} else {
|
|
614
|
+
r = safeJsonParse(wasmBindings.jsql_find_by_ids(table, JSON.stringify(ids)));
|
|
615
|
+
}
|
|
616
|
+
if (r && r.error) throw new Error(r.error);
|
|
617
|
+
this._runHooks('afterFind', [table, { ids }, r]);
|
|
618
|
+
const schema = this._schemas[table];
|
|
619
|
+
if (schema && Array.isArray(r)) return r.map(row => restoreRow(row, schema));
|
|
620
|
+
return r;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
findByIdsRaw(table, ids) {
|
|
624
|
+
return this.findByIds(table, ids);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
async updateById(table, id, data) {
|
|
628
|
+
if (!this._runHooks('beforeUpdate', [table, id, data])) return;
|
|
629
|
+
await this._flush();
|
|
630
|
+
const seq = this._seqIdByPk(table, id);
|
|
631
|
+
if (seq === null) return null;
|
|
632
|
+
const r = safeJsonParse(wasmBindings.jsql_update_by_id(table, BigInt(seq), JSON.stringify(data)));
|
|
633
|
+
if (r && r.ok === false) throw new Error(r.error || 'update failed');
|
|
634
|
+
this._emit('update', { table, id, data });
|
|
635
|
+
this._runHooks('afterUpdate', [table, id, data, r]);
|
|
636
|
+
return r;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
async removeById(table, id) {
|
|
640
|
+
if (!this._runHooks('beforeDelete', [table, id])) return;
|
|
641
|
+
await this._flush();
|
|
642
|
+
const seq = this._seqIdByPk(table, id);
|
|
643
|
+
if (seq === null) return null;
|
|
644
|
+
const r = safeJsonParse(wasmBindings.jsql_remove_by_id(table, BigInt(seq)));
|
|
645
|
+
if (r && r.ok === false) throw new Error(r.error || 'remove failed');
|
|
646
|
+
this._emit('delete', { table, id });
|
|
647
|
+
this._runHooks('afterDelete', [table, id, r]);
|
|
648
|
+
return r;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
async beginTx() {
|
|
652
|
+
await this._flush();
|
|
653
|
+
const r = safeJsonParse(wasmBindings.jsql_begin_tx());
|
|
654
|
+
if (r && r.ok === false) throw new Error(r.error || 'begin transaction failed');
|
|
655
|
+
this._txId = r.txId;
|
|
656
|
+
return r.txId;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async commitTx(txId) {
|
|
660
|
+
const r = safeJsonParse(wasmBindings.jsql_commit_tx(String(txId)));
|
|
661
|
+
if (r && r.ok === false) throw new Error(r.error || 'commit transaction failed');
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
async rollbackTx(txId) {
|
|
666
|
+
const r = safeJsonParse(wasmBindings.jsql_rollback_tx(String(txId)));
|
|
667
|
+
if (r && r.ok === false) throw new Error(r.error || 'rollback transaction failed');
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
async beginTransaction() {
|
|
672
|
+
return this.beginTx();
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
async commit() {
|
|
676
|
+
if (this._txId === undefined) return true;
|
|
677
|
+
const r = await this.commitTx(this._txId);
|
|
678
|
+
this._txId = undefined;
|
|
679
|
+
return r;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async rollback() {
|
|
683
|
+
if (this._txId === undefined) return true;
|
|
684
|
+
const r = await this.rollbackTx(this._txId);
|
|
685
|
+
this._txId = undefined;
|
|
686
|
+
return r;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
async hasTable(name) {
|
|
690
|
+
return this._tableNames.has(name);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async getTables() {
|
|
694
|
+
return Array.from(this._tableNames);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
async getTableSchema(name) {
|
|
698
|
+
return this._schemas[name] || null;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* 直接执行原生 SQL 语句。本实例自身即充当 engine,
|
|
703
|
+
* 支持 SELECT / INSERT / UPDATE / DELETE / CREATE TABLE 等常用语句。
|
|
704
|
+
* @example
|
|
705
|
+
* const rows = await db.query("SELECT * FROM users WHERE age > ?", [21]);
|
|
706
|
+
*/
|
|
707
|
+
async query(sql, paramsOrOpts, opts) {
|
|
708
|
+
return executeSQL(this, sql, paramsOrOpts, opts);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
async stop() {
|
|
712
|
+
await this._flush();
|
|
713
|
+
this._runHooks('onStop', []);
|
|
714
|
+
this._emit('stop', {});
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
module.exports = { JSQL };
|