jsql-neo 4.1.1 → 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/lib/mysql_server.js +73 -9
- package/lib/sql.js +13 -1
- package/package.json +1 -1
package/lib/mysql_server.js
CHANGED
|
@@ -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
|
|
529
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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')) {
|
|
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
|
}
|