mirror of
https://github.com/hanzoai/better-sqlite3.git
synced 2026-09-24 16:57:15 +00:00
implemented Database#explain()
This commit is contained in:
+14
@@ -11,6 +11,7 @@
|
||||
- [Database#prepare()](#preparestring---statement) (see [`Statement`](#class-statement))
|
||||
- [Database#transaction()](#transactionfunction---function)
|
||||
- [Database#pragma()](#pragmastring-options---results)
|
||||
- [Database#explain()](#explainstring---array-of-rows)
|
||||
- [Database#backup()](#backupdestination-options---promise)
|
||||
- [Database#serialize()](#serializeoptions---buffer)
|
||||
- [Database#function()](#functionname-options-function---this)
|
||||
@@ -126,6 +127,19 @@ If execution of the PRAGMA fails, an `Error` is thrown.
|
||||
|
||||
It's better to use this method instead of normal [prepared statements](#preparestring---statement) when executing PRAGMA, because this method normalizes some odd behavior that may otherwise be experienced. The documentation on SQLite PRAGMA can be found [here](https://www.sqlite.org/pragma.html).
|
||||
|
||||
### .explain(*string*) -> *array of rows*
|
||||
|
||||
Runs [`EXPLAIN`](https://www.sqlite.org/eqp.html) on the given SQL string and returns the result. By default this returns the low-level [bytecode](https://www.sqlite.org/opcode.html) program that SQLite would use to execute the statement (one row per virtual machine instruction). To get the higher-level [query plan](https://www.sqlite.org/eqp.html) instead, prefix your SQL with `QUERY PLAN`.
|
||||
|
||||
```js
|
||||
db.explain('SELECT * FROM cats WHERE name = ?');
|
||||
db.explain('QUERY PLAN SELECT * FROM cats WHERE name = ?');
|
||||
```
|
||||
|
||||
Unlike normal [prepared statements](#preparestring---statement), the SQL passed to this method may contain [parameters](#binding-parameters) that are left unbound. Since `EXPLAIN` only inspects the statement rather than executing it, no parameter values are needed.
|
||||
|
||||
If the given SQL is invalid, an `Error` is thrown.
|
||||
|
||||
### .backup(*destination*, [*options*]) -> *promise*
|
||||
|
||||
Initiates a [backup](https://www.sqlite.org/backup.html) of the database, returning a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) for when the backup is complete. If the backup fails, the promise will be rejected with an `Error`. You can optionally backup an attached database instead by setting the `attached` option to the name of the desired attached database. A backup file is just a regular SQLite database file. It can be opened by [`new Database()`](#new-databasepath-options) just like any SQLite database.
|
||||
|
||||
@@ -64,6 +64,7 @@ module.exports = function createDatabase(getAddon, allowNativeBinding) {
|
||||
Database.prototype.prepare = wrappers.prepare;
|
||||
Database.prototype.transaction = require('./methods/transaction');
|
||||
Database.prototype.pragma = require('./methods/pragma');
|
||||
Database.prototype.explain = require('./methods/explain');
|
||||
Database.prototype.backup = require('./methods/backup');
|
||||
Database.prototype.serialize = require('./methods/serialize');
|
||||
Database.prototype.function = require('./methods/function');
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
const { cppdb } = require('../util');
|
||||
|
||||
module.exports = function explain(source) {
|
||||
if (typeof source !== 'string') throw new TypeError('Expected first argument to be a string');
|
||||
|
||||
const stmt = this[cppdb].prepare(`EXPLAIN ${source}`, this, false, true);
|
||||
return stmt.all();
|
||||
};
|
||||
@@ -7,6 +7,6 @@ module.exports = function pragma(source, options) {
|
||||
if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object');
|
||||
const simple = getBooleanOption(options, 'simple');
|
||||
|
||||
const stmt = this[cppdb].prepare(`PRAGMA ${source}`, this, true);
|
||||
const stmt = this[cppdb].prepare(`PRAGMA ${source}`, this, true, false);
|
||||
return simple ? stmt.pluck().get() : stmt.all();
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const { cppdb } = require('../util');
|
||||
|
||||
exports.prepare = function prepare(sql) {
|
||||
return this[cppdb].prepare(sql, this, false);
|
||||
return this[cppdb].prepare(sql, this, false, false);
|
||||
};
|
||||
|
||||
exports.exec = function exec(sql) {
|
||||
|
||||
@@ -199,9 +199,11 @@ NODE_METHOD(Database::JS_prepare) {
|
||||
REQUIRE_ARGUMENT_STRING(first, Napi::String source);
|
||||
REQUIRE_ARGUMENT_OBJECT(second, Napi::Object database);
|
||||
REQUIRE_ARGUMENT_BOOLEAN(third, bool pragmaMode);
|
||||
REQUIRE_ARGUMENT_BOOLEAN(fourth, bool explainMode);
|
||||
(void)source;
|
||||
(void)database;
|
||||
(void)pragmaMode;
|
||||
(void)explainMode;
|
||||
UseAddon;
|
||||
UseIsolate;
|
||||
Napi::Function c = addon->Statement.Value();
|
||||
|
||||
@@ -89,12 +89,16 @@ NODE_METHOD(Statement::JS_new) {
|
||||
Napi::String source = pinfo[0].As<Napi::String>();
|
||||
Napi::Object database = pinfo[1].As<Napi::Object>();
|
||||
bool pragmaMode = pinfo[2].As<Napi::Boolean>().Value();
|
||||
bool explainMode = pinfo[3].As<Napi::Boolean>().Value();
|
||||
int flags = SQLITE_PREPARE_PERSISTENT;
|
||||
|
||||
if (pragmaMode) {
|
||||
REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db->GetState());
|
||||
flags = 0;
|
||||
}
|
||||
if (explainMode) {
|
||||
flags = 0;
|
||||
}
|
||||
|
||||
UseIsolate;
|
||||
std::string utf8 = source.Utf8Value();
|
||||
@@ -140,6 +144,7 @@ NODE_METHOD(Statement::JS_new) {
|
||||
this->db = db;
|
||||
this->handle = handle;
|
||||
this->extras = new Extras(env, addon->RowFactory.Value(), addon->ArrayFactory.Value(), addon->NextId());
|
||||
this->bound = explainMode;
|
||||
this->safe_ints = db->GetState()->safe_ints;
|
||||
this->returns_data = returns_data;
|
||||
this->alive = true;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
const Database = require('../.');
|
||||
|
||||
describe('Database#explain()', function () {
|
||||
beforeEach(function () {
|
||||
this.db = new Database(util.next());
|
||||
this.db.prepare('CREATE TABLE entries (a TEXT, b INTEGER)').run();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.db.close();
|
||||
});
|
||||
|
||||
it('should throw an exception if a string is not provided', function () {
|
||||
expect(() => this.db.explain(123)).to.throw(TypeError);
|
||||
expect(() => this.db.explain(0)).to.throw(TypeError);
|
||||
expect(() => this.db.explain(null)).to.throw(TypeError);
|
||||
expect(() => this.db.explain()).to.throw(TypeError);
|
||||
expect(() => this.db.explain(new String('SELECT * FROM entries'))).to.throw(TypeError);
|
||||
});
|
||||
it('should throw an exception if invalid SQL is provided', function () {
|
||||
expect(() => this.db.explain('SELECT * FROM nonexistent')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR');
|
||||
expect(() => this.db.explain('CREATE TABLE entries (a TEXT')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR');
|
||||
});
|
||||
it('should return the bytecode program as an array of rows', function () {
|
||||
const program = this.db.explain('SELECT * FROM entries');
|
||||
expect(program).to.be.an('array');
|
||||
expect(program.length).to.be.above(0);
|
||||
for (const row of program) {
|
||||
expect(row).to.be.an('object');
|
||||
expect(row.addr).to.be.a('number');
|
||||
expect(row.opcode).to.be.a('string');
|
||||
expect(row).to.have.keys(['addr', 'opcode', 'p1', 'p2', 'p3', 'p4', 'p5', 'comment']);
|
||||
}
|
||||
expect(program.some(row => row.opcode === 'Halt')).to.be.true;
|
||||
});
|
||||
it('should explain statements with unbound parameters', function () {
|
||||
// A normal prepared statement cannot be executed without its parameters.
|
||||
expect(() => this.db.prepare('SELECT * FROM entries WHERE a = ?').all()).to.throw(RangeError);
|
||||
|
||||
const program = this.db.explain('SELECT * FROM entries WHERE a = ?');
|
||||
expect(program).to.be.an('array');
|
||||
expect(program.length).to.be.above(0);
|
||||
});
|
||||
it('should explain statements with multiple unbound parameters', function () {
|
||||
const program = this.db.explain('SELECT * FROM entries WHERE a = ? AND b = @b');
|
||||
expect(program).to.be.an('array');
|
||||
expect(program.length).to.be.above(0);
|
||||
});
|
||||
it('should return the query plan when prefixed with QUERY PLAN', function () {
|
||||
const plan = this.db.explain('QUERY PLAN SELECT * FROM entries');
|
||||
expect(plan).to.be.an('array');
|
||||
expect(plan.length).to.be.above(0);
|
||||
for (const row of plan) {
|
||||
expect(row).to.be.an('object');
|
||||
expect(row).to.have.keys(['id', 'parent', 'notused', 'detail']);
|
||||
expect(row.detail).to.be.a('string');
|
||||
}
|
||||
expect(plan.some(row => /entries/.test(row.detail))).to.be.true;
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user