jsql-neo 4.2.0 → 4.4.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/README.md +112 -94
- package/bin/jsql +138 -0
- package/index.d.ts +351 -0
- package/index.js +18 -0
- package/lib/migrate.js +242 -0
- package/lib/mysql_server.js +335 -6
- package/lib/redis_server.js +448 -0
- package/lib/sql.js +533 -87
- package/lib/table.js +4 -2
- package/lib/web_ui.js +226 -0
- package/package.json +66 -47
- package/test/smoke.js +58 -0
- package/wasm/browser.d.ts +88 -0
- package/wasm/browser.mjs +404 -0
- package/wasm/browser_bg.mjs +462 -0
package/README.md
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
# JSQL-NEO v4.
|
|
1
|
+
# JSQL-NEO v4.0.1
|
|
2
2
|
|
|
3
|
-
Rust-powered embedded database with **three engines + SQL +
|
|
4
|
-
|
|
5
|
-
> **Docs & Help:** [https://help.vexify.top](https://help.vexify.top)
|
|
6
|
-
> **GitHub:** [https://github.com/vexify-org/JSQL-neo](https://github.com/vexify-org/JSQL-neo)
|
|
3
|
+
Rust-powered embedded database with **three engines + SQL + Redis-style storage** in one npm package.
|
|
7
4
|
|
|
8
5
|
## Engines
|
|
9
6
|
|
|
@@ -33,54 +30,7 @@ await jsql.executeSQL(db, 'DELETE FROM users WHERE id = 2');
|
|
|
33
30
|
await db.stop();
|
|
34
31
|
```
|
|
35
32
|
|
|
36
|
-
Supported
|
|
37
|
-
|
|
38
|
-
Type names accept MySQL aliases: `INTEGER/INT/BIGINT/TINYINT/SMALLINT`, `STRING/TEXT/VARCHAR(n)/CHAR(n)`, `FLOAT/DOUBLE/REAL/NUMERIC/DECIMAL`, `BOOLEAN/BOOL`, `DATE/DATETIME/TIMESTAMP`, `JSON/OBJECT/ARRAY`.
|
|
39
|
-
|
|
40
|
-
## MySQL-Compatible Server (multi-database)
|
|
41
|
-
|
|
42
|
-
A native MySQL protocol server on TCP 3306 — any `mysql` / `mysql2` / GUI client connects directly.
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
npx jsql server start --port 3306 --data-dir ./data # 后台常驻
|
|
46
|
-
npx jsql server status # 状态
|
|
47
|
-
npx jsql server stop # 停止
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
**Multi-database & 一键建表:** each database is an independent directory (`<dataDir>/<db>`).
|
|
51
|
-
|
|
52
|
-
```sql
|
|
53
|
-
-- 随意连接即可直接建表(自动落到默认库 default)
|
|
54
|
-
CREATE TABLE users (id INTEGER PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50));
|
|
55
|
-
|
|
56
|
-
-- 一条 SQL 跨库建库+建表(库不存在自动创建)
|
|
57
|
-
CREATE TABLE shop.orders (id INTEGER PRIMARY KEY, amount FLOAT, `user` STRING);
|
|
58
|
-
|
|
59
|
-
-- 连接时指定尚不存在的库会自动创建:mysql -uroot -D mydb
|
|
60
|
-
-- 查询/增删改也能带库前缀:INSERT INTO shop.orders ..., SELECT * FROM shop.orders, SHOW TABLES FROM shop;
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
- 连接指定未存在的库 → **自动建库**,无需先 `CREATE DATABASE`
|
|
64
|
-
- 未 `USE` 任何库 → 落到默认库,可直接 `CREATE TABLE`
|
|
65
|
-
- 全量多库 DDL/DML 支持 `db.table` 点号语法 + `SHOW TABLES FROM db`
|
|
66
|
-
- **多用户 + 库级权限**: 每个用户可配置可访问的数据库白名单(`{ user: { password, databases: [...] } }`)
|
|
67
|
-
|
|
68
|
-
```json
|
|
69
|
-
{ "port": 3306, "dataDir": "/var/lib/jsql",
|
|
70
|
-
"auth": { "admin": { "password": "secret", "databases": ["shop", "blog"] }, "reader": "readpass" } }
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Transactions
|
|
74
|
-
|
|
75
|
-
```js
|
|
76
|
-
db.begin(); // 默认 REPEATABLE_READ:begin 时保存行快照
|
|
77
|
-
db.insert('t', { id: 2 });
|
|
78
|
-
db.rollback(); // 删除新增、还原更新、恢复自增 —— 回滚生效
|
|
79
|
-
db.begin(); db.insert('t', { id: 3 }); db.commit();
|
|
80
|
-
// 也支持 SQL:BEGIN ... COMMIT / ROLLBACK
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
`begin(isolationLevel)` 可选 `'REPEATABLE_READ'`(快照回滚,默认)或 `'READ_COMMITTED'`(仅回滚自增)。
|
|
33
|
+
Supported: `CREATE/DROP TABLE`, `INSERT` (multi-row, `ON DUPLICATE KEY UPDATE`), `SELECT` (`WHERE`/`ORDER BY`/`LIMIT`/`OFFSET`/`GROUP BY`/`HAVING`/aggregates), `UPDATE`, `DELETE`, prepared statements with `?` placeholders.
|
|
84
34
|
|
|
85
35
|
## Redis-Style Storage Modes
|
|
86
36
|
|
|
@@ -154,58 +104,126 @@ db.save();
|
|
|
154
104
|
| `updateById(table, id, data)` | ✅ | ✅ | ✅ | O(1) PK update |
|
|
155
105
|
| `removeById(table, id)` | ✅ | ✅ | ✅ | O(1) PK delete |
|
|
156
106
|
| `dropTable(name)` | ✅ | ✅ | ✅ | Remove table |
|
|
157
|
-
| `insertMany(table, items)` | ✅ | ✅ | ✅ | Batch insert |
|
|
158
|
-
| `update(table, query, updates)` | ✅ | ✅ | ✅ | Batch update by query |
|
|
159
|
-
| `delete(table, query)` | ✅ | ✅ | ✅ | Batch delete by query |
|
|
160
|
-
| `truncate(table)` | ✅ | ✅ | ✅ | Delete all rows |
|
|
161
|
-
| `begin()/commit()/rollback()` | ✅ | ✅ | ✅ | Transactions (snapshot rollback) |
|
|
162
107
|
| `executeSQL(db, sql, params?)` | ✅ | ✅ | ✅ | Run SQL statements |
|
|
163
108
|
|
|
164
109
|
## Schema Field Options
|
|
165
110
|
|
|
166
111
|
```js
|
|
167
112
|
{
|
|
168
|
-
type: 'string' | 'integer' | 'float' | 'boolean'
|
|
169
|
-
primaryKey: true, // PK field (auto-indexed)
|
|
113
|
+
type: 'string' | 'integer' | 'float' | 'boolean',
|
|
114
|
+
primaryKey: true, // PK field (auto-indexed)
|
|
170
115
|
autoIncrement: true, // Auto-generate integer PK
|
|
171
|
-
unique: true, // Unique constraint (auto-indexed)
|
|
172
116
|
length: 32, // Max string length
|
|
173
117
|
default: 'value', // Default value
|
|
174
|
-
nullable: true
|
|
175
|
-
required: true, // Not-null constraint
|
|
176
|
-
foreignKey: { table, field, onDelete }, // FK constraint
|
|
177
|
-
check: (val) => bool, // CHECK constraint
|
|
178
|
-
computed: (row) => val // Computed field
|
|
118
|
+
nullable: true // Allow null
|
|
179
119
|
}
|
|
180
120
|
```
|
|
181
121
|
|
|
122
|
+
## ORM Compatibility
|
|
123
|
+
|
|
124
|
+
The MySQL-compatible server mode accepts standard MySQL clients, so popular Node.js ORMs work without a plugin. Tested against a live `createMysqlServer()` instance:
|
|
125
|
+
|
|
126
|
+
| ORM | Version | Results |
|
|
127
|
+
|-----|---------|---------|
|
|
128
|
+
| [Sequelize](https://sequelize.org) | v6 | ✅ 10/10 — connect, authenticate, sync (CREATE TABLE), create, bulkCreate, find, count, update, destroy, MAX() |
|
|
129
|
+
| [Knex](https://knexjs.org) | v3 | ✅ 9/9 — schema builder, insert, select, where + orderBy, count, update, delete, raw `SELECT VERSION()` |
|
|
130
|
+
| [TypeORM](https://typeorm.io) | v0.3 | ✅ 8/8 — initialize, synchronize, save, findOne, find, count, update, delete |
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
// Sequelize
|
|
134
|
+
const { Sequelize, DataTypes } = require('sequelize');
|
|
135
|
+
const sequelize = new Sequelize('default', 'root', '', {
|
|
136
|
+
host: '127.0.0.1', port: 33306, dialect: 'mysql',
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Knex
|
|
140
|
+
const knex = require('knex')({
|
|
141
|
+
client: 'mysql2',
|
|
142
|
+
connection: { host: '127.0.0.1', port: 33306, user: 'root', database: 'default' },
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// TypeORM
|
|
146
|
+
const ds = new DataSource({
|
|
147
|
+
type: 'mysql',
|
|
148
|
+
host: '127.0.0.1', port: 33306, username: 'root', database: 'default',
|
|
149
|
+
synchronize: true, entities: [User],
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Supported for ORM compatibility: prepared statements (`COM_STMT_PREPARE`/`EXECUTE`), binary protocol result sets, `SHOW COLUMNS` / `SHOW INDEX` / `SHOW CREATE TABLE` / `SHOW VARIABLES` / `SHOW GRANTS`, `information_schema` queries, `START TRANSACTION`, `TRUNCATE TABLE`, `SET` statements, scalar functions (`VERSION()`, `NOW()`, `CONCAT()`, `IFNULL()`, ...), and MySQL DDL forms (`int unsigned`, `auto_increment`, `ENGINE=InnoDB`, `DEFAULT CHARSET`).
|
|
154
|
+
|
|
155
|
+
## Redis-compatible server
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
jsql redis -p 6379 --data-dir ./redis-data [--auth secret]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
or from code:
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
const { createRedisServer } = require('jsql-neo');
|
|
165
|
+
createRedisServer({ port: 6379, dataDir: './redis-data' }).listen();
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Speaks RESP2 (works with `redis-cli` / ioredis / node-redis). Strings, hashes, lists, sets, counters, TTL, `KEYS`/`EXISTS`/`DEL`, multi-select DBs, `AUTH`, and snapshot persistence to `data.rdb.json` (debounced 500ms + on shutdown).
|
|
169
|
+
|
|
170
|
+
## Web UI
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
jsql ui -p 8080 --data-dir ./data # then open http://localhost:8080
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
or from code: `new WebUI({ port: 8080, dataDir: './data' }).start()`. Zero-dependency management console: browse databases/tables, run SQL in the browser.
|
|
177
|
+
|
|
178
|
+
## CLI
|
|
179
|
+
|
|
180
|
+
`jsql serve` (foreground MySQL server), `jsql server start|stop|status` (background daemon), `jsql import <dump.sql|.json|.csv>`, `jsql export <table> <file>`, `jsql bench`, `jsql redis`, `jsql ui`, `jsql mod` (plugin registry), `jsql version`.
|
|
181
|
+
|
|
182
|
+
## Browser playground
|
|
183
|
+
|
|
184
|
+
`examples/playground/` is a self-contained SQL playground that runs the full engine in the browser (WASM + IndexedDB, no server):
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
cd examples/playground && npm install && npm run dev
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Testing
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
npm test # zero-dependency SQL engine smoke tests
|
|
194
|
+
npm run test:orms # ORM compatibility suites (start examples/orms/start-server.js first)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The ORM suites in `examples/orms/` run Sequelize (10/10), Knex (9/9) and TypeORM (8/8) against a live MySQL-compatible server.
|
|
198
|
+
|
|
199
|
+
## Benchmark
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
cd bench && npm install && npm run bench
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`bench/bench.js` compares the four engines on 100k rows (insert / point query / range query / count / update):
|
|
206
|
+
|
|
207
|
+
| Engine | Insert/s | Point query (500x) | Range query (500x) | Total |
|
|
208
|
+
|--------|----------|--------------------|--------------------|-------|
|
|
209
|
+
| **Native** (Rust N-API) | 0.66M | 930ms | 685ms | **1.77s** |
|
|
210
|
+
| better-sqlite3 (WAL) | 0.40M | 3258ms | 149ms | 3.66s |
|
|
211
|
+
| sql.js (WASM sqlite) | 0.30M | 5852ms | 366ms | 6.57s |
|
|
212
|
+
| Pure JS | 0.38M | 11278ms | 18138ms | 29.7s |
|
|
213
|
+
|
|
214
|
+
Native is ~2x faster than better-sqlite3 and ~17x faster than the pure-JS engine overall (Linux x64 / Node 24). Full breakdown: `bench/report.md`.
|
|
215
|
+
|
|
182
216
|
## Features
|
|
183
217
|
|
|
184
|
-
|
|
185
|
-
-
|
|
186
|
-
- Redis-style storage
|
|
187
|
-
-
|
|
188
|
-
-
|
|
189
|
-
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
|
|
196
|
-
**MySQL Server & CLI**
|
|
197
|
-
- MySQL protocol server (TCP 3306), compatible with `mysql`/`mysql2`/GUI clients
|
|
198
|
-
- Multi-database (one dir per DB), auto-create on connect / on `db.table` usage, default-DB fallback for one-click `CREATE TABLE`
|
|
199
|
-
- Multi-user authentication with per-database access lists (`1044`/`1049` enforcement)
|
|
200
|
-
- `jsql server start/stop/status` background daemon with graceful shutdown
|
|
201
|
-
|
|
202
|
-
**Data API**
|
|
203
|
-
- Typed schema with constraints: PK / auto-increment / unique / required / length / default / FK / CHECK / computed fields
|
|
204
|
-
- Batch insert / update / delete, cursor-based pagination, upsert
|
|
205
|
-
- Transactions with snapshot rollback (default `REPEATABLE_READ`)
|
|
206
|
-
- CSV import/export, JSON schema validation, soft delete, versioning, views, triggers, attachable databases
|
|
207
|
-
- Hooks & change events (`beforeInsert`/`afterUpdate`/…) for plugins and audit
|
|
208
|
-
|
|
209
|
-
**Security**
|
|
210
|
-
- SQL injection hardening: comments/`LOAD_FILE`/dangerous statements blocked by default, ReDoS-safe regex
|
|
211
|
-
- Rate-limited auth (max auth fails), oversized-row protection, safe name/path validation (`..` blocked)
|
|
218
|
+
- Three engines: Native (N-API Rust), WASM (wasm-pack Rust), Pure JS (local JSON)
|
|
219
|
+
- SQL engine with prepared statements
|
|
220
|
+
- Redis-style hybrid/disk storage: memory-first + async flush + LRU eviction + lazy reload
|
|
221
|
+
- O(1) primary key hash index (`FxHashMap` / `Map`)
|
|
222
|
+
- B-Tree indexing for range queries
|
|
223
|
+
- WAL + snapshot crash recovery (server engine)
|
|
224
|
+
- Batch insert / update / delete
|
|
225
|
+
- Cursor-based pagination
|
|
226
|
+
- Transaction support (server engine)
|
|
227
|
+
- Built-in web management UI (`jsql ui` / `new WebUI(...)`): browse databases & tables, run SQL from the browser
|
|
228
|
+
- CLI: `jsql serve|server|import|export|bench|ui|mod|version`
|
|
229
|
+
- Migration tools: mysqldump import, JSON/CSV export/import
|
package/bin/jsql
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
2
4
|
const yaggs = require('@vexify-org/yaggs');
|
|
3
5
|
const { ModuleManager } = require('../lib/mod');
|
|
6
|
+
const Database = require('../lib/database');
|
|
7
|
+
const migrate = require('../lib/migrate');
|
|
4
8
|
|
|
5
9
|
function printModules(list) {
|
|
6
10
|
if (list.length === 0) {
|
|
@@ -93,6 +97,140 @@ const cli = yaggs()
|
|
|
93
97
|
fail(e);
|
|
94
98
|
}
|
|
95
99
|
})
|
|
100
|
+
.command('export', 'Export a table to JSON or CSV', (sub) => {
|
|
101
|
+
sub.option('db', { type: 'string', description: 'Database file path (default ./db.json)' });
|
|
102
|
+
sub.option('json', { type: 'boolean', description: 'Output metadata as JSON' });
|
|
103
|
+
}, async (argv) => {
|
|
104
|
+
const table = argv._[0];
|
|
105
|
+
const outFile = argv._[1];
|
|
106
|
+
const fail = (e) => { console.error(`Error: ${e.message}`); process.exitCode = 1; };
|
|
107
|
+
try {
|
|
108
|
+
if (!table || !outFile) throw new Error('Usage: jsql export <table> <file.json|csv> [--db <path>]');
|
|
109
|
+
const db = new Database(argv.db || './db.json', { autoSave: false });
|
|
110
|
+
await db.loadDatabase ? db.loadDatabase() : null;
|
|
111
|
+
const bytes = await migrate.exportToFile(db, table, outFile);
|
|
112
|
+
if (argv.json) console.log(JSON.stringify({ ok: true, table, file: outFile, bytes }));
|
|
113
|
+
else console.log(`Exported '${table}' (${bytes} bytes) to ${outFile}`);
|
|
114
|
+
} catch (e) { fail(e); }
|
|
115
|
+
})
|
|
116
|
+
.command('import', 'Import a mysqldump (.sql), JSON (.json) or CSV (.csv) file', (sub) => {
|
|
117
|
+
sub.option('db', { type: 'string', description: 'Database file path (default ./db.json)' });
|
|
118
|
+
sub.option('table', { type: 'string', description: 'Target table for CSV import' });
|
|
119
|
+
sub.option('strict', { type: 'boolean', description: 'Abort on first import error' });
|
|
120
|
+
sub.option('json', { type: 'boolean', description: 'Output metadata as JSON' });
|
|
121
|
+
}, async (argv) => {
|
|
122
|
+
const file = argv._[0];
|
|
123
|
+
const fail = (e) => { console.error(`Error: ${e.message}`); process.exitCode = 1; };
|
|
124
|
+
try {
|
|
125
|
+
if (!file) throw new Error('Usage: jsql import <file.sql|.json|.csv> [--db <path>] [--table <name>]');
|
|
126
|
+
const db = new Database(argv.db || './db.json', { autoSave: false });
|
|
127
|
+
await db.loadDatabase ? db.loadDatabase() : null;
|
|
128
|
+
const ext = path.extname(file).toLowerCase();
|
|
129
|
+
let result;
|
|
130
|
+
if (ext === '.sql') {
|
|
131
|
+
result = await migrate.importDumpFile(db, file, { strict: !!argv.strict });
|
|
132
|
+
} else if (ext === '.json') {
|
|
133
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
134
|
+
result = await migrate.importFromJSON(db, data);
|
|
135
|
+
} else if (ext === '.csv') {
|
|
136
|
+
if (!argv.table) throw new Error('CSV import requires --table <name>');
|
|
137
|
+
const schemaFile = file.replace(/\.csv$/i, '.schema.json');
|
|
138
|
+
const schema = fs.existsSync(schemaFile) ? JSON.parse(fs.readFileSync(schemaFile, 'utf8')) : null;
|
|
139
|
+
result = await migrate.importFromCSV(db, argv.table, fs.readFileSync(file, 'utf8'), { schema });
|
|
140
|
+
} else {
|
|
141
|
+
throw new Error('Unsupported file type: ' + ext);
|
|
142
|
+
}
|
|
143
|
+
if (argv.json) {
|
|
144
|
+
console.log(JSON.stringify({ ok: true, ...result }));
|
|
145
|
+
} else {
|
|
146
|
+
const errs = result.errors && result.errors.length;
|
|
147
|
+
console.log(`Imported: ${result.created ? result.created.length + ' table(s), ' : ''}${result.inserted || 0} row(s)` + (errs ? `, ${errs} error(s)` : ''));
|
|
148
|
+
}
|
|
149
|
+
} catch (e) { fail(e); }
|
|
150
|
+
})
|
|
151
|
+
.command('serve', 'Run the MySQL-compatible server in the foreground', (sub) => {
|
|
152
|
+
sub.option('port', { alias: 'p', type: 'number', description: 'Listen port (default 3306)' });
|
|
153
|
+
sub.option('host', { type: 'string', description: 'Listen host (default 127.0.0.1)' });
|
|
154
|
+
sub.option('data-dir', { type: 'string', description: 'Directory to store databases' });
|
|
155
|
+
sub.option('no-auth', { type: 'boolean', description: 'Allow connections without authentication' });
|
|
156
|
+
sub.option('log', { type: 'boolean', description: 'Log queries to stdout' });
|
|
157
|
+
}, async (argv) => {
|
|
158
|
+
const { createMysqlServer } = require('../lib/mysql_server');
|
|
159
|
+
const options = {
|
|
160
|
+
port: argv.port != null ? argv.port : 3306,
|
|
161
|
+
host: argv.host || '127.0.0.1',
|
|
162
|
+
dataDir: argv['data-dir'],
|
|
163
|
+
noAuth: argv['no-auth'] === true,
|
|
164
|
+
};
|
|
165
|
+
if (argv.log) options.onQuery = (q) => console.log('[sql]', q);
|
|
166
|
+
const srv = createMysqlServer(options);
|
|
167
|
+
srv.listen();
|
|
168
|
+
console.log(`jsql-neo MySQL server on ${options.host}:${options.port}${options.dataDir ? ' (data: ' + options.dataDir + ')' : ' (in-memory)'}`);
|
|
169
|
+
setInterval(() => {}, 1 << 30);
|
|
170
|
+
})
|
|
171
|
+
.command('bench', 'Insert + query benchmark against a data directory', (sub) => {
|
|
172
|
+
sub.option('n', { type: 'number', description: 'Number of rows (default 10000)' });
|
|
173
|
+
sub.option('data-dir', { type: 'string', description: 'Data directory (default :memory:)' });
|
|
174
|
+
sub.option('json', { type: 'boolean', description: 'Output as JSON' });
|
|
175
|
+
}, async (argv) => {
|
|
176
|
+
const N = argv.n || 10000;
|
|
177
|
+
const dataDir = argv['data-dir'] || ':memory:';
|
|
178
|
+
try {
|
|
179
|
+
const db = new Database(dataDir === ':memory:' ? ':memory:' : path.join(dataDir, 'bench.json'), { autoSave: false });
|
|
180
|
+
await db.createTable('bench', { id: { type: 'integer', primaryKey: true, autoIncrement: true }, name: { type: 'string' }, score: { type: 'float' } });
|
|
181
|
+
let t0 = Date.now();
|
|
182
|
+
const batch = [];
|
|
183
|
+
for (let i = 0; i < N; i++) batch.push({ name: 'user_' + i, score: Math.random() * 1000 });
|
|
184
|
+
for (let i = 0; i < batch.length; i += 1000) await db.insert('bench', batch.slice(i, i + 1000));
|
|
185
|
+
const insertMs = Date.now() - t0;
|
|
186
|
+
t0 = Date.now();
|
|
187
|
+
let hits = 0;
|
|
188
|
+
for (let i = 0; i < 100; i++) hits += (await db.find('bench', { score: { $gt: 500 } }, { limit: 10 })).length;
|
|
189
|
+
const queryMs = Date.now() - t0;
|
|
190
|
+
const total = await db.count('bench');
|
|
191
|
+
const out = { ok: true, rows: N, inserted: total, insertMs, insertPerSec: Math.round(N / (insertMs / 1000)), queryMs, queryCount: 100 };
|
|
192
|
+
if (argv.json) console.log(JSON.stringify(out));
|
|
193
|
+
else console.log(`Inserted ${total} rows in ${insertMs}ms (${out.insertPerSec}/s), 100 queries in ${queryMs}ms`);
|
|
194
|
+
} catch (e) { console.error(`Error: ${e.message}`); process.exitCode = 1; }
|
|
195
|
+
})
|
|
196
|
+
.command('ui', 'Serve the built-in web management console', (sub) => {
|
|
197
|
+
sub.option('port', { alias: 'p', type: 'number', description: 'HTTP port (default 8080)' });
|
|
198
|
+
sub.option('host', { type: 'string', description: 'Listen host (default 0.0.0.0)' });
|
|
199
|
+
sub.option('data-dir', { type: 'string', description: 'Directory containing *.json databases (default .)' });
|
|
200
|
+
sub.option('readonly', { type: 'boolean', description: 'Never write back to disk' });
|
|
201
|
+
}, async (argv) => {
|
|
202
|
+
const { WebUI } = require('../lib/web_ui');
|
|
203
|
+
const ui = new WebUI({
|
|
204
|
+
port: argv.port != null ? argv.port : 8080,
|
|
205
|
+
host: argv.host || '0.0.0.0',
|
|
206
|
+
dataDir: argv['data-dir'] || '.',
|
|
207
|
+
readonly: argv.readonly === true,
|
|
208
|
+
});
|
|
209
|
+
try {
|
|
210
|
+
const port = await ui.start();
|
|
211
|
+
console.log(`JSQL-NEO web UI on http://${argv.host || '0.0.0.0'}:${port} (data: ${ui.dataDir})`);
|
|
212
|
+
setInterval(() => {}, 1 << 30);
|
|
213
|
+
} catch (e) { console.error(`Error: ${e.message}`); process.exitCode = 1; }
|
|
214
|
+
})
|
|
215
|
+
.command('redis', 'Run the Redis-compatible server', (sub) => {
|
|
216
|
+
sub.option('port', { alias: 'p', type: 'number', description: 'Listen port (default 6379)' });
|
|
217
|
+
sub.option('host', { type: 'string', description: 'Listen host (default 127.0.0.1)' });
|
|
218
|
+
sub.option('data-dir', { type: 'string', description: 'Directory for data.rdb.json snapshot' });
|
|
219
|
+
sub.option('auth', { type: 'string', description: 'Require this password for AUTH' });
|
|
220
|
+
sub.option('log', { type: 'boolean', description: 'Log commands to stdout' });
|
|
221
|
+
}, (argv) => {
|
|
222
|
+
const { createRedisServer } = require('../lib/redis_server');
|
|
223
|
+
const srv = createRedisServer({
|
|
224
|
+
port: argv.port != null ? argv.port : 6379,
|
|
225
|
+
host: argv.host || '127.0.0.1',
|
|
226
|
+
dataDir: argv['data-dir'],
|
|
227
|
+
password: argv.auth || null,
|
|
228
|
+
onQuery: argv.log ? (c) => console.log('[redis]', c.join(' ')) : null,
|
|
229
|
+
});
|
|
230
|
+
srv.listen();
|
|
231
|
+
console.log(`jsql-neo Redis server on ${argv.host || '127.0.0.1'}:${argv.port != null ? argv.port : 6379}${argv['data-dir'] ? ' (data: ' + argv['data-dir'] + ')' : ''}`);
|
|
232
|
+
setInterval(() => {}, 1 << 30);
|
|
233
|
+
})
|
|
96
234
|
.command('version', 'Show version', null, () => {
|
|
97
235
|
console.log(require('../package.json').version);
|
|
98
236
|
})
|