jsql-neo 6.3.6 → 6.3.10
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 +78 -8239
- package/index.js +1 -1
- package/lib/cache.js +33 -27
- package/lib/client.js +11 -2
- package/lib/database.js +370 -56
- package/lib/date-types.js +51 -13
- package/lib/errors.js +4 -1
- package/lib/jsql_format.js +30 -21
- package/lib/mod.js +27 -6
- package/lib/mongo_server.js +143 -26
- package/lib/mysql_compat.js +57 -11
- package/lib/mysql_server.js +104 -4
- package/lib/nedb_compat.js +9 -6
- package/lib/pg_server.js +4 -11
- package/lib/plugin.js +20 -2
- package/lib/prepared.js +10 -0
- package/lib/query.js +9 -2
- package/lib/redis_server.js +88 -16
- package/lib/sql.js +856 -81
- package/lib/sqlite_compat.js +2 -1
- package/lib/sqlite_worker.js +8 -2
- package/lib/table.js +68 -26
- package/lib/tui.js +1 -1
- package/lib/web_ui.js +24 -8
- package/native/jsql-neo-native.node +0 -0
- package/package.json +10 -3
- package/test/alter-table.test.js +289 -0
- package/test/identifier-keywords.test.js +140 -0
- package/test/index-ddl.test.js +203 -0
- package/test/mongo-group.test.js +105 -0
- package/test/null-semantics.test.js +126 -0
- package/test/recursive-cte.test.js +123 -0
- package/test/savepoint.test.js +388 -0
- package/test/sql-parser.test.js +30 -5
package/index.js
CHANGED
package/lib/cache.js
CHANGED
|
@@ -36,7 +36,8 @@ class Cache {
|
|
|
36
36
|
return count;
|
|
37
37
|
}
|
|
38
38
|
keys(pattern = '*') {
|
|
39
|
-
const
|
|
39
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
40
|
+
const regex = new RegExp('^' + escaped + '$');
|
|
40
41
|
const result = [];
|
|
41
42
|
for (const key of this._getData().keys()) { if (!this._isExpired(key) && regex.test(key)) result.push(key); }
|
|
42
43
|
return result;
|
|
@@ -47,8 +48,9 @@ class Cache {
|
|
|
47
48
|
const val = this._getData().get(k);
|
|
48
49
|
if (val === undefined) return 'none';
|
|
49
50
|
if (typeof val === 'string') return 'string';
|
|
50
|
-
if (Array.isArray(val)) return val._type === 'hash' ? 'hash' : 'list';
|
|
51
51
|
if (val instanceof Set) return 'set';
|
|
52
|
+
if (Array.isArray(val)) return val._type === 'hash' ? 'hash' : 'list';
|
|
53
|
+
if (typeof val === 'object' && val._type === 'hash') return 'hash';
|
|
52
54
|
return 'string';
|
|
53
55
|
}
|
|
54
56
|
expire(key, seconds) {
|
|
@@ -175,6 +177,8 @@ class Cache {
|
|
|
175
177
|
if (!list || !Array.isArray(list) || list._type === 'hash') return [];
|
|
176
178
|
if (start < 0) start = Math.max(list.length + start, 0);
|
|
177
179
|
if (stop < 0) stop = list.length + stop;
|
|
180
|
+
stop = Math.min(stop, list.length - 1);
|
|
181
|
+
if (start > stop) return [];
|
|
178
182
|
return list.slice(start, stop + 1);
|
|
179
183
|
}
|
|
180
184
|
|
|
@@ -244,32 +248,34 @@ class Cache {
|
|
|
244
248
|
|
|
245
249
|
save() {
|
|
246
250
|
if (!this._persistPath) return;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
251
|
+
try {
|
|
252
|
+
const dump = { version: 1, databases: [] };
|
|
253
|
+
for (let i = 0; i < this._dbs.length; i++) {
|
|
254
|
+
const db = this._dbs[i];
|
|
255
|
+
if (!db || db.size === 0) continue;
|
|
256
|
+
const data = {};
|
|
257
|
+
for (const [key, val] of db) {
|
|
258
|
+
const exp = this._dbsExpires[i];
|
|
259
|
+
if (exp && exp.has(key) && exp.get(key) <= Date.now()) continue;
|
|
260
|
+
if (val instanceof Set) data[key] = { _t: 'set', _v: [...val] };
|
|
261
|
+
else if (Array.isArray(val)) data[key] = { _t: 'list', _v: val };
|
|
262
|
+
else if (typeof val === 'object' && val._type === 'hash') {
|
|
263
|
+
const clean = {}; for (const [f, v] of Object.entries(val)) { if (f !== '_type') clean[f] = v; }
|
|
264
|
+
data[key] = { _t: 'hash', _v: clean };
|
|
265
|
+
} else data[key] = { _t: 'string', _v: val };
|
|
266
|
+
}
|
|
267
|
+
const expires = {};
|
|
268
|
+
const expMap = this._dbsExpires[i];
|
|
269
|
+
if (expMap) { for (const [key, ttl] of expMap) { if (ttl > Date.now()) expires[key] = ttl; } }
|
|
270
|
+
if (Object.keys(data).length > 0 || Object.keys(expires).length > 0) dump.databases.push({ index: i, data, expires });
|
|
261
271
|
}
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
fs.writeFileSync(this._persistPath + '.tmp', JSON.stringify(dump), 'utf8');
|
|
270
|
-
fs.renameSync(this._persistPath + '.tmp', this._persistPath);
|
|
271
|
-
this._dirty = false;
|
|
272
|
-
return 'OK';
|
|
272
|
+
const dir = path.dirname(this._persistPath);
|
|
273
|
+
if (dir) fs.mkdirSync(dir, { recursive: true });
|
|
274
|
+
fs.writeFileSync(this._persistPath + '.tmp', JSON.stringify(dump), 'utf8');
|
|
275
|
+
fs.renameSync(this._persistPath + '.tmp', this._persistPath);
|
|
276
|
+
this._dirty = false;
|
|
277
|
+
return 'OK';
|
|
278
|
+
} catch (e) { return null; }
|
|
273
279
|
}
|
|
274
280
|
|
|
275
281
|
_load() {
|
package/lib/client.js
CHANGED
|
@@ -12,7 +12,8 @@ function binPath() {
|
|
|
12
12
|
class JSQL {
|
|
13
13
|
constructor(opts = {}) {
|
|
14
14
|
this.host = opts.host || '127.0.0.1';
|
|
15
|
-
|
|
15
|
+
// P3-5: 默认端口 6379 会与 Redis 冲突,改为 3000
|
|
16
|
+
this.port = opts.port || 3000;
|
|
16
17
|
this.dataDir = opts.dataDir || null;
|
|
17
18
|
this._process = null;
|
|
18
19
|
}
|
|
@@ -45,7 +46,8 @@ class JSQL {
|
|
|
45
46
|
async stop() {
|
|
46
47
|
if (!this._process) return;
|
|
47
48
|
this._process.kill();
|
|
48
|
-
|
|
49
|
+
// P3-6: 不再硬等 500ms,给进程一点时间退出即可
|
|
50
|
+
await new Promise(r => setTimeout(r, 100));
|
|
49
51
|
this._process = null;
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -74,6 +76,13 @@ class JSQL {
|
|
|
74
76
|
headers: body ? { 'Content-Type': 'application/json' } : {},
|
|
75
77
|
};
|
|
76
78
|
const req = http.request(opts, (res) => {
|
|
79
|
+
// P3-7: 未处理 res 错误——非 2xx 状态码应 reject
|
|
80
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
81
|
+
let errData = '';
|
|
82
|
+
res.on('data', c => errData += c);
|
|
83
|
+
res.on('end', () => reject(new Error(`HTTP ${res.statusCode}: ${errData}`)));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
77
86
|
let data = '';
|
|
78
87
|
res.on('data', c => data += c);
|
|
79
88
|
res.on('end', () => {
|