jsql-neo 4.1.0 → 4.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,10 @@
1
- # JSQL-NEO v4.0.1
1
+ # JSQL-NEO v4.1.1
2
2
 
3
3
  Rust-powered embedded database with **three engines + SQL + Redis-style storage** in one npm package.
4
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)
7
+
5
8
  ## Engines
6
9
 
7
10
  | Engine | Entry | Use Case |
@@ -458,6 +458,7 @@ class MysqlConnection {
458
458
  return;
459
459
  }
460
460
  try {
461
+ if (!this.server._dbExists(db)) await this.server.createDatabase(db);
461
462
  await this.server._getDatabase(db);
462
463
  this.currentDb = db;
463
464
  } catch (e) {
@@ -525,8 +526,10 @@ class MysqlConnection {
525
526
  default:
526
527
  break;
527
528
  }
528
- const engine = await this.server._getEngine(this.currentDb);
529
- const r = await executeSQL(engine, stmtSql, {
529
+ const routed = await this.server._route(stmt, stmtSql, this.currentDb, this.user);
530
+ const engine = routed.engine;
531
+ const sqlToRun = routed.sql;
532
+ const r = await executeSQL(engine, sqlToRun, {
530
533
  allowComments: this.server.allowComments,
531
534
  safety: this.server.safety,
532
535
  maxStatements: 1,
@@ -571,6 +574,7 @@ class MysqlServer {
571
574
  this._ownEngine = false;
572
575
  this._databases = new Map();
573
576
  this._dbDir = options.dataDir ? path.resolve(options.dataDir) : null;
577
+ this._defaultDbName = options.defaultDatabase || 'default';
574
578
  this._connectionCounter = 0;
575
579
  this._sockets = new Set();
576
580
  this._securityHandler = typeof options.onSecurityEvent === 'function' ? options.onSecurityEvent : null;
@@ -585,9 +589,7 @@ class MysqlServer {
585
589
  async _getEngine(dbName) {
586
590
  if (dbName) return this._getDatabase(dbName);
587
591
  if (this._dbDir) {
588
- const err = new Error(`No database selected`);
589
- err.code = 1046;
590
- throw err;
592
+ return this._getDatabase(this._defaultDbName, { autoCreate: true });
591
593
  }
592
594
  if (this._engine) return this._engine;
593
595
  if (this.options.engine || this.options.database) {
@@ -601,6 +603,64 @@ class MysqlServer {
601
603
  return this._engine;
602
604
  }
603
605
 
606
+ _dbExists(name) {
607
+ if (this._databases.has(name)) return true;
608
+ const dir = this._dbPath(name);
609
+ return !!(dir && fs.existsSync(dir));
610
+ }
611
+
612
+ // 语句路由:解析库前缀(db.table)与 SHOW TABLES FROM db,
613
+ // 决定执行引擎,并把 db.table 改写为 table 后交给对应库引擎执行。
614
+ async _route(stmt, sql, currentDb, user) {
615
+ if (stmt.type === 'showTables' && stmt.database) {
616
+ return { engine: await this._getDatabase(stmt.database), sql };
617
+ }
618
+ const tables = [];
619
+ const collect = (s) => {
620
+ if (!s) return;
621
+ if (s.type === 'createTable') tables.push(s.name);
622
+ else if (s.type === 'dropTable') tables.push(s.table);
623
+ else if (s.type === 'insert') tables.push(s.name);
624
+ else if (s.type === 'update') tables.push(s.table);
625
+ else if (s.type === 'delete') tables.push(s.table);
626
+ else if (s.type === 'describe') tables.push(s.table);
627
+ else if (s.type === 'select' && s.from) {
628
+ for (const t of s.from.tables) tables.push(t.table);
629
+ for (const j of s.from.joins) tables.push(j.item.table);
630
+ }
631
+ };
632
+ collect(stmt);
633
+ let db = null;
634
+ for (const t of tables) {
635
+ if (t && t.indexOf('.') !== -1) {
636
+ const d = t.split('.')[0];
637
+ if (db && d !== db) {
638
+ const err = new Error(`Cross-database references are not supported in one statement`);
639
+ err.code = 1105;
640
+ throw err;
641
+ }
642
+ db = d;
643
+ }
644
+ }
645
+ if (db) {
646
+ if (!this._dbExists(db)) await this.createDatabase(db);
647
+ const engine = await this._getDatabase(db);
648
+ let s = sql;
649
+ for (const t of tables) {
650
+ if (t && t.indexOf('.') !== -1) {
651
+ s = s.replace(new RegExp('\\b' + t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'g'), t.split('.')[1]);
652
+ }
653
+ }
654
+ return { engine, sql: s };
655
+ }
656
+ if (this._dbDir && !currentDb && this.auth && !this._canAccessDb(user, this._defaultDbName)) {
657
+ const err = new Error('No database selected');
658
+ err.code = 1046;
659
+ throw err;
660
+ }
661
+ return { engine: await this._getEngine(currentDb), sql };
662
+ }
663
+
604
664
  _safeDbName(name) {
605
665
  if (typeof name !== 'string' || name.length === 0 || name.length > 64) return false;
606
666
  if (!/^[a-zA-Z0-9_$.\-]+$/.test(name)) return false;
@@ -614,7 +674,7 @@ class MysqlServer {
614
674
  return path.join(this._dbDir, name);
615
675
  }
616
676
 
617
- async _getDatabase(name) {
677
+ async _getDatabase(name, opts = {}) {
618
678
  if (!this._safeDbName(name)) {
619
679
  const err = new Error(`Unknown database '${name}'`);
620
680
  err.code = 1049;
@@ -623,9 +683,13 @@ class MysqlServer {
623
683
  if (this._databases.has(name)) return this._databases.get(name);
624
684
  const dir = this._dbPath(name);
625
685
  if (dir && !fs.existsSync(dir)) {
626
- const err = new Error(`Unknown database '${name}'`);
627
- err.code = 1049;
628
- throw err;
686
+ if (opts.autoCreate) {
687
+ fs.mkdirSync(dir, { recursive: true });
688
+ } else {
689
+ const err = new Error(`Unknown database '${name}'`);
690
+ err.code = 1049;
691
+ throw err;
692
+ }
629
693
  }
630
694
  const engine = dir ? new Database(dir, { mode: 'hybrid' }) : new Database(':memory:');
631
695
  if (typeof engine.start === 'function') await engine.start();
package/lib/sql.js CHANGED
@@ -227,6 +227,12 @@ class Parser {
227
227
  parseTableName() {
228
228
  const t = this.next();
229
229
  if (t.type !== 'ident') throw new Error(`Expected table name, got '${t.value}'`);
230
+ if (this.peek().type === 'op' && this.peek().value === '.') {
231
+ this.next();
232
+ const t2 = this.next();
233
+ if (t2.type !== 'ident') throw new Error(`Expected table name after '.', got '${t2.value}'`);
234
+ return t.value + '.' + t2.value;
235
+ }
230
236
  return t.value;
231
237
  }
232
238
 
@@ -668,7 +674,13 @@ class Parser {
668
674
 
669
675
  parseShow() {
670
676
  this.expectKeyword('SHOW');
671
- if (this.isKeyword('TABLES')) { this.next(); this.optionalTailSemicolon(); return { type: 'showTables' }; }
677
+ if (this.isKeyword('TABLES')) {
678
+ this.next();
679
+ let database = null;
680
+ if (this.isKeyword('FROM')) { this.next(); database = this.parseTableName(); }
681
+ this.optionalTailSemicolon();
682
+ return { type: 'showTables', database };
683
+ }
672
684
  if (this.isKeyword('DATABASES')) { this.next(); this.optionalTailSemicolon(); return { type: 'showDatabases' }; }
673
685
  throw new Error('Unsupported SHOW statement');
674
686
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsql-neo",
3
- "version": "4.1.0",
3
+ "version": "4.1.2",
4
4
  "description": "JSQL-NEO — Rust-powered embedded database with WASM, REST API, B-Tree indexes, WAL, crash recovery",
5
5
  "main": "index.js",
6
6
  "bin": {