sqlite,test,doc: allow Buffer and URL as database location
PR-URL: https://github.com/nodejs/node/pull/56991 Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
parent
269c851240
commit
c7cf6778c7
@ -77,20 +77,24 @@ console.log(query.all());
|
|||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v22.5.0
|
added: v22.5.0
|
||||||
|
changes:
|
||||||
|
- version: REPLACEME
|
||||||
|
pr-url: https://github.com/nodejs/node/pull/56991
|
||||||
|
description: The `path` argument now supports Buffer and URL objects.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
This class represents a single [connection][] to a SQLite database. All APIs
|
This class represents a single [connection][] to a SQLite database. All APIs
|
||||||
exposed by this class execute synchronously.
|
exposed by this class execute synchronously.
|
||||||
|
|
||||||
### `new DatabaseSync(location[, options])`
|
### `new DatabaseSync(path[, options])`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v22.5.0
|
added: v22.5.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
* `location` {string} The location of the database. A SQLite database can be
|
* `path` {string | Buffer | URL} The path of the database. A SQLite database can be
|
||||||
stored in a file or completely [in memory][]. To use a file-backed database,
|
stored in a file or completely [in memory][]. To use a file-backed database,
|
||||||
the location should be a file path. To use an in-memory database, the location
|
the path should be a file path. To use an in-memory database, the path
|
||||||
should be the special name `':memory:'`.
|
should be the special name `':memory:'`.
|
||||||
* `options` {Object} Configuration options for the database connection. The
|
* `options` {Object} Configuration options for the database connection. The
|
||||||
following options are supported:
|
following options are supported:
|
||||||
@ -200,7 +204,7 @@ wrapper around [`sqlite3_create_function_v2()`][].
|
|||||||
added: v22.5.0
|
added: v22.5.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
Opens the database specified in the `location` argument of the `DatabaseSync`
|
Opens the database specified in the `path` argument of the `DatabaseSync`
|
||||||
constructor. This method should only be used when the database is not opened via
|
constructor. This method should only be used when the database is not opened via
|
||||||
the constructor. An exception is thrown if the database is already open.
|
the constructor. An exception is thrown if the database is already open.
|
||||||
|
|
||||||
@ -534,15 +538,19 @@ exception.
|
|||||||
| `TEXT` | {string} |
|
| `TEXT` | {string} |
|
||||||
| `BLOB` | {TypedArray} or {DataView} |
|
| `BLOB` | {TypedArray} or {DataView} |
|
||||||
|
|
||||||
## `sqlite.backup(sourceDb, destination[, options])`
|
## `sqlite.backup(sourceDb, path[, options])`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v23.8.0
|
added: v23.8.0
|
||||||
|
changes:
|
||||||
|
- version: REPLACEME
|
||||||
|
pr-url: https://github.com/nodejs/node/pull/56991
|
||||||
|
description: The `path` argument now supports Buffer and URL objects.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
* `sourceDb` {DatabaseSync} The database to backup. The source database must be open.
|
* `sourceDb` {DatabaseSync} The database to backup. The source database must be open.
|
||||||
* `destination` {string} The path where the backup will be created. If the file already exists, the contents will be
|
* `path` {string | Buffer | URL} The path where the backup will be created. If the file already exists,
|
||||||
overwritten.
|
the contents will be overwritten.
|
||||||
* `options` {Object} Optional configuration for the backup. The
|
* `options` {Object} Optional configuration for the backup. The
|
||||||
following properties are supported:
|
following properties are supported:
|
||||||
* `source` {string} Name of the source database. This can be `'main'` (the default primary database) or any other
|
* `source` {string} Name of the source database. This can be `'main'` (the default primary database) or any other
|
||||||
|
@ -194,6 +194,7 @@
|
|||||||
V(host_string, "host") \
|
V(host_string, "host") \
|
||||||
V(hostmaster_string, "hostmaster") \
|
V(hostmaster_string, "hostmaster") \
|
||||||
V(hostname_string, "hostname") \
|
V(hostname_string, "hostname") \
|
||||||
|
V(href_string, "href") \
|
||||||
V(http_1_1_string, "http/1.1") \
|
V(http_1_1_string, "http/1.1") \
|
||||||
V(id_string, "id") \
|
V(id_string, "id") \
|
||||||
V(identity_string, "identity") \
|
V(identity_string, "identity") \
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "node.h"
|
#include "node.h"
|
||||||
#include "node_errors.h"
|
#include "node_errors.h"
|
||||||
#include "node_mem-inl.h"
|
#include "node_mem-inl.h"
|
||||||
|
#include "node_url.h"
|
||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
#include "threadpoolwork-inl.h"
|
#include "threadpoolwork-inl.h"
|
||||||
#include "util-inl.h"
|
#include "util-inl.h"
|
||||||
@ -181,10 +182,11 @@ class BackupJob : public ThreadPoolWork {
|
|||||||
void ScheduleBackup() {
|
void ScheduleBackup() {
|
||||||
Isolate* isolate = env()->isolate();
|
Isolate* isolate = env()->isolate();
|
||||||
HandleScope handle_scope(isolate);
|
HandleScope handle_scope(isolate);
|
||||||
backup_status_ = sqlite3_open_v2(destination_name_.c_str(),
|
backup_status_ = sqlite3_open_v2(
|
||||||
&dest_,
|
destination_name_.c_str(),
|
||||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
|
&dest_,
|
||||||
nullptr);
|
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI,
|
||||||
|
nullptr);
|
||||||
Local<Promise::Resolver> resolver =
|
Local<Promise::Resolver> resolver =
|
||||||
Local<Promise::Resolver>::New(env()->isolate(), resolver_);
|
Local<Promise::Resolver>::New(env()->isolate(), resolver_);
|
||||||
if (backup_status_ != SQLITE_OK) {
|
if (backup_status_ != SQLITE_OK) {
|
||||||
@ -503,11 +505,14 @@ bool DatabaseSync::Open() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(cjihrig): Support additional flags.
|
// TODO(cjihrig): Support additional flags.
|
||||||
|
int default_flags = SQLITE_OPEN_URI;
|
||||||
int flags = open_config_.get_read_only()
|
int flags = open_config_.get_read_only()
|
||||||
? SQLITE_OPEN_READONLY
|
? SQLITE_OPEN_READONLY
|
||||||
: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
|
: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
|
||||||
int r = sqlite3_open_v2(
|
int r = sqlite3_open_v2(open_config_.location().c_str(),
|
||||||
open_config_.location().c_str(), &connection_, flags, nullptr);
|
&connection_,
|
||||||
|
flags | default_flags,
|
||||||
|
nullptr);
|
||||||
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
|
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
|
||||||
|
|
||||||
r = sqlite3_db_config(connection_,
|
r = sqlite3_db_config(connection_,
|
||||||
@ -585,27 +590,85 @@ bool DatabaseSync::ShouldIgnoreSQLiteError() {
|
|||||||
return ignore_next_sqlite_error_;
|
return ignore_next_sqlite_error_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> ValidateDatabasePath(Environment* env,
|
||||||
|
Local<Value> path,
|
||||||
|
const std::string& field_name) {
|
||||||
|
auto has_null_bytes = [](const std::string& str) {
|
||||||
|
return str.find('\0') != std::string::npos;
|
||||||
|
};
|
||||||
|
std::string location;
|
||||||
|
if (path->IsString()) {
|
||||||
|
location = Utf8Value(env->isolate(), path.As<String>()).ToString();
|
||||||
|
if (!has_null_bytes(location)) {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path->IsUint8Array()) {
|
||||||
|
Local<Uint8Array> buffer = path.As<Uint8Array>();
|
||||||
|
size_t byteOffset = buffer->ByteOffset();
|
||||||
|
size_t byteLength = buffer->ByteLength();
|
||||||
|
auto data =
|
||||||
|
static_cast<const uint8_t*>(buffer->Buffer()->Data()) + byteOffset;
|
||||||
|
if (!(std::find(data, data + byteLength, 0) != data + byteLength)) {
|
||||||
|
Local<Value> out;
|
||||||
|
if (String::NewFromUtf8(env->isolate(),
|
||||||
|
reinterpret_cast<const char*>(data),
|
||||||
|
NewStringType::kNormal,
|
||||||
|
static_cast<int>(byteLength))
|
||||||
|
.ToLocal(&out)) {
|
||||||
|
return Utf8Value(env->isolate(), out.As<String>()).ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When is URL
|
||||||
|
if (path->IsObject()) {
|
||||||
|
Local<Object> url = path.As<Object>();
|
||||||
|
Local<Value> href;
|
||||||
|
Local<Value> protocol;
|
||||||
|
if (url->Get(env->context(), env->href_string()).ToLocal(&href) &&
|
||||||
|
href->IsString() &&
|
||||||
|
url->Get(env->context(), env->protocol_string()).ToLocal(&protocol) &&
|
||||||
|
protocol->IsString()) {
|
||||||
|
location = Utf8Value(env->isolate(), href.As<String>()).ToString();
|
||||||
|
if (!has_null_bytes(location)) {
|
||||||
|
auto file_url = ada::parse(location);
|
||||||
|
CHECK(file_url);
|
||||||
|
if (file_url->type != ada::scheme::FILE) {
|
||||||
|
THROW_ERR_INVALID_URL_SCHEME(env->isolate());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
|
||||||
|
"The \"%s\" argument must be a string, "
|
||||||
|
"Uint8Array, or URL without null bytes.",
|
||||||
|
field_name.c_str());
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
|
void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
|
||||||
if (!args.IsConstructCall()) {
|
if (!args.IsConstructCall()) {
|
||||||
THROW_ERR_CONSTRUCT_CALL_REQUIRED(env);
|
THROW_ERR_CONSTRUCT_CALL_REQUIRED(env);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!args[0]->IsString()) {
|
std::optional<std::string> location =
|
||||||
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
|
ValidateDatabasePath(env, args[0], "path");
|
||||||
"The \"path\" argument must be a string.");
|
if (!location.has_value()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string location =
|
DatabaseOpenConfiguration open_config(std::move(location.value()));
|
||||||
Utf8Value(env->isolate(), args[0].As<String>()).ToString();
|
|
||||||
DatabaseOpenConfiguration open_config(std::move(location));
|
|
||||||
|
|
||||||
bool open = true;
|
bool open = true;
|
||||||
bool allow_load_extension = false;
|
bool allow_load_extension = false;
|
||||||
|
|
||||||
if (args.Length() > 1) {
|
if (args.Length() > 1) {
|
||||||
if (!args[1]->IsObject()) {
|
if (!args[1]->IsObject()) {
|
||||||
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
|
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
|
||||||
@ -984,17 +1047,15 @@ void Backup(const FunctionCallbackInfo<Value>& args) {
|
|||||||
DatabaseSync* db;
|
DatabaseSync* db;
|
||||||
ASSIGN_OR_RETURN_UNWRAP(&db, args[0].As<Object>());
|
ASSIGN_OR_RETURN_UNWRAP(&db, args[0].As<Object>());
|
||||||
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
|
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
|
||||||
if (!args[1]->IsString()) {
|
std::optional<std::string> dest_path =
|
||||||
THROW_ERR_INVALID_ARG_TYPE(
|
ValidateDatabasePath(env, args[1], "path");
|
||||||
env->isolate(), "The \"destination\" argument must be a string.");
|
if (!dest_path.has_value()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rate = 100;
|
int rate = 100;
|
||||||
std::string source_db = "main";
|
std::string source_db = "main";
|
||||||
std::string dest_db = "main";
|
std::string dest_db = "main";
|
||||||
|
|
||||||
Utf8Value dest_path(env->isolate(), args[1].As<String>());
|
|
||||||
Local<Function> progressFunc = Local<Function>();
|
Local<Function> progressFunc = Local<Function>();
|
||||||
|
|
||||||
if (args.Length() > 2) {
|
if (args.Length() > 2) {
|
||||||
@ -1077,12 +1138,11 @@ void Backup(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
args.GetReturnValue().Set(resolver->GetPromise());
|
args.GetReturnValue().Set(resolver->GetPromise());
|
||||||
|
|
||||||
BackupJob* job = new BackupJob(env,
|
BackupJob* job = new BackupJob(env,
|
||||||
db,
|
db,
|
||||||
resolver,
|
resolver,
|
||||||
std::move(source_db),
|
std::move(source_db),
|
||||||
*dest_path,
|
dest_path.value(),
|
||||||
std::move(dest_db),
|
std::move(dest_db),
|
||||||
rate,
|
rate,
|
||||||
progressFunc);
|
progressFunc);
|
||||||
|
@ -4,6 +4,7 @@ import { join } from 'node:path';
|
|||||||
import { backup, DatabaseSync } from 'node:sqlite';
|
import { backup, DatabaseSync } from 'node:sqlite';
|
||||||
import { describe, test } from 'node:test';
|
import { describe, test } from 'node:test';
|
||||||
import { writeFileSync } from 'node:fs';
|
import { writeFileSync } from 'node:fs';
|
||||||
|
import { pathToFileURL } from 'node:url';
|
||||||
|
|
||||||
let cnt = 0;
|
let cnt = 0;
|
||||||
|
|
||||||
@ -13,8 +14,8 @@ function nextDb() {
|
|||||||
return join(tmpdir.path, `database-${cnt++}.db`);
|
return join(tmpdir.path, `database-${cnt++}.db`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeSourceDb() {
|
function makeSourceDb(dbPath = ':memory:') {
|
||||||
const database = new DatabaseSync(':memory:');
|
const database = new DatabaseSync(dbPath);
|
||||||
|
|
||||||
database.exec(`
|
database.exec(`
|
||||||
CREATE TABLE data(
|
CREATE TABLE data(
|
||||||
@ -42,21 +43,39 @@ describe('backup()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('throws if path is not a string', (t) => {
|
test('throws if path is not a string, URL, or Buffer', (t) => {
|
||||||
const database = makeSourceDb();
|
const database = makeSourceDb();
|
||||||
|
|
||||||
t.assert.throws(() => {
|
t.assert.throws(() => {
|
||||||
backup(database);
|
backup(database);
|
||||||
}, {
|
}, {
|
||||||
code: 'ERR_INVALID_ARG_TYPE',
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
message: 'The "destination" argument must be a string.'
|
message: 'The "path" argument must be a string, Uint8Array, or URL without null bytes.'
|
||||||
});
|
});
|
||||||
|
|
||||||
t.assert.throws(() => {
|
t.assert.throws(() => {
|
||||||
backup(database, {});
|
backup(database, {});
|
||||||
}, {
|
}, {
|
||||||
code: 'ERR_INVALID_ARG_TYPE',
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
message: 'The "destination" argument must be a string.'
|
message: 'The "path" argument must be a string, Uint8Array, or URL without null bytes.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws if the database path contains null bytes', (t) => {
|
||||||
|
const database = makeSourceDb();
|
||||||
|
|
||||||
|
t.assert.throws(() => {
|
||||||
|
backup(database, Buffer.from('l\0cation'));
|
||||||
|
}, {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
message: 'The "path" argument must be a string, Uint8Array, or URL without null bytes.'
|
||||||
|
});
|
||||||
|
|
||||||
|
t.assert.throws(() => {
|
||||||
|
backup(database, 'l\0cation');
|
||||||
|
}, {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
message: 'The "path" argument must be a string, Uint8Array, or URL without null bytes.'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -141,6 +160,46 @@ test('database backup', async (t) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('backup database using location as URL', async (t) => {
|
||||||
|
const database = makeSourceDb();
|
||||||
|
const destDb = pathToFileURL(nextDb());
|
||||||
|
|
||||||
|
t.after(() => { database.close(); });
|
||||||
|
|
||||||
|
await backup(database, destDb);
|
||||||
|
|
||||||
|
const backupDb = new DatabaseSync(destDb);
|
||||||
|
|
||||||
|
t.after(() => { backupDb.close(); });
|
||||||
|
|
||||||
|
const rows = backupDb.prepare('SELECT * FROM data').all();
|
||||||
|
|
||||||
|
t.assert.deepStrictEqual(rows, [
|
||||||
|
{ __proto__: null, key: 1, value: 'value-1' },
|
||||||
|
{ __proto__: null, key: 2, value: 'value-2' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('backup database using location as Buffer', async (t) => {
|
||||||
|
const database = makeSourceDb();
|
||||||
|
const destDb = Buffer.from(nextDb());
|
||||||
|
|
||||||
|
t.after(() => { database.close(); });
|
||||||
|
|
||||||
|
await backup(database, destDb);
|
||||||
|
|
||||||
|
const backupDb = new DatabaseSync(destDb);
|
||||||
|
|
||||||
|
t.after(() => { backupDb.close(); });
|
||||||
|
|
||||||
|
const rows = backupDb.prepare('SELECT * FROM data').all();
|
||||||
|
|
||||||
|
t.assert.deepStrictEqual(rows, [
|
||||||
|
{ __proto__: null, key: 1, value: 'value-1' },
|
||||||
|
{ __proto__: null, key: 2, value: 'value-2' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test('database backup in a single call', async (t) => {
|
test('database backup in a single call', async (t) => {
|
||||||
const progressFn = t.mock.fn();
|
const progressFn = t.mock.fn();
|
||||||
const database = makeSourceDb();
|
const database = makeSourceDb();
|
||||||
@ -179,6 +238,19 @@ test('throws exception when trying to start backup from a closed database', (t)
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('throws if URL is not file: scheme', (t) => {
|
||||||
|
const database = new DatabaseSync(':memory:');
|
||||||
|
|
||||||
|
t.after(() => { database.close(); });
|
||||||
|
|
||||||
|
t.assert.throws(() => {
|
||||||
|
backup(database, new URL('http://example.com/backup.db'));
|
||||||
|
}, {
|
||||||
|
code: 'ERR_INVALID_URL_SCHEME',
|
||||||
|
message: 'The URL must be of scheme file:',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('database backup fails when dest file is not writable', async (t) => {
|
test('database backup fails when dest file is not writable', async (t) => {
|
||||||
const readonlyDestDb = nextDb();
|
const readonlyDestDb = nextDb();
|
||||||
writeFileSync(readonlyDestDb, '', { mode: 0o444 });
|
writeFileSync(readonlyDestDb, '', { mode: 0o444 });
|
||||||
@ -225,7 +297,7 @@ test('backup fails when source db is invalid', async (t) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('backup fails when destination cannot be opened', async (t) => {
|
test('backup fails when path cannot be opened', async (t) => {
|
||||||
const database = makeSourceDb();
|
const database = makeSourceDb();
|
||||||
|
|
||||||
await t.assert.rejects(async () => {
|
await t.assert.rejects(async () => {
|
||||||
|
@ -23,12 +23,30 @@ suite('DatabaseSync() constructor', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('throws if database path is not a string', (t) => {
|
test('throws if database path is not a string, Uint8Array, or URL', (t) => {
|
||||||
t.assert.throws(() => {
|
t.assert.throws(() => {
|
||||||
new DatabaseSync();
|
new DatabaseSync();
|
||||||
}, {
|
}, {
|
||||||
code: 'ERR_INVALID_ARG_TYPE',
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
message: /The "path" argument must be a string/,
|
message: /The "path" argument must be a string, Uint8Array, or URL without null bytes/,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws if the database location as Buffer contains null bytes', (t) => {
|
||||||
|
t.assert.throws(() => {
|
||||||
|
new DatabaseSync(Buffer.from('l\0cation'));
|
||||||
|
}, {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
message: 'The "path" argument must be a string, Uint8Array, or URL without null bytes.',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws if the database location as string contains null bytes', (t) => {
|
||||||
|
t.assert.throws(() => {
|
||||||
|
new DatabaseSync('l\0cation');
|
||||||
|
}, {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
message: 'The "path" argument must be a string, Uint8Array, or URL without null bytes.',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -256,6 +274,15 @@ suite('DatabaseSync.prototype.exec()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('throws if the URL does not have the file: scheme', (t) => {
|
||||||
|
t.assert.throws(() => {
|
||||||
|
new DatabaseSync(new URL('http://example.com'));
|
||||||
|
}, {
|
||||||
|
code: 'ERR_INVALID_URL_SCHEME',
|
||||||
|
message: 'The URL must be of scheme file:',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('throws if database is not open', (t) => {
|
test('throws if database is not open', (t) => {
|
||||||
const db = new DatabaseSync(nextDb(), { open: false });
|
const db = new DatabaseSync(nextDb(), { open: false });
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ const tmpdir = require('../common/tmpdir');
|
|||||||
const { join } = require('node:path');
|
const { join } = require('node:path');
|
||||||
const { DatabaseSync, constants } = require('node:sqlite');
|
const { DatabaseSync, constants } = require('node:sqlite');
|
||||||
const { suite, test } = require('node:test');
|
const { suite, test } = require('node:test');
|
||||||
|
const { pathToFileURL } = require('node:url');
|
||||||
let cnt = 0;
|
let cnt = 0;
|
||||||
|
|
||||||
tmpdir.refresh();
|
tmpdir.refresh();
|
||||||
@ -111,3 +112,101 @@ test('math functions are enabled', (t) => {
|
|||||||
{ __proto__: null, pi: 3.141592653589793 },
|
{ __proto__: null, pi: 3.141592653589793 },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Buffer is supported as the database path', (t) => {
|
||||||
|
const db = new DatabaseSync(Buffer.from(nextDb()));
|
||||||
|
t.after(() => { db.close(); });
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE data(key INTEGER PRIMARY KEY);
|
||||||
|
INSERT INTO data (key) VALUES (1);
|
||||||
|
`);
|
||||||
|
|
||||||
|
t.assert.deepStrictEqual(
|
||||||
|
db.prepare('SELECT * FROM data').all(),
|
||||||
|
[{ __proto__: null, key: 1 }]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('URL is supported as the database path', (t) => {
|
||||||
|
const url = pathToFileURL(nextDb());
|
||||||
|
const db = new DatabaseSync(url);
|
||||||
|
t.after(() => { db.close(); });
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE data(key INTEGER PRIMARY KEY);
|
||||||
|
INSERT INTO data (key) VALUES (1);
|
||||||
|
`);
|
||||||
|
|
||||||
|
t.assert.deepStrictEqual(
|
||||||
|
db.prepare('SELECT * FROM data').all(),
|
||||||
|
[{ __proto__: null, key: 1 }]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
suite('URI query params', () => {
|
||||||
|
const baseDbPath = nextDb();
|
||||||
|
const baseDb = new DatabaseSync(baseDbPath);
|
||||||
|
baseDb.exec(`
|
||||||
|
CREATE TABLE data(key INTEGER PRIMARY KEY);
|
||||||
|
INSERT INTO data (key) VALUES (1);
|
||||||
|
`);
|
||||||
|
baseDb.close();
|
||||||
|
|
||||||
|
test('query params are supported with URL objects', (t) => {
|
||||||
|
const url = pathToFileURL(baseDbPath);
|
||||||
|
url.searchParams.set('mode', 'ro');
|
||||||
|
const readOnlyDB = new DatabaseSync(url);
|
||||||
|
t.after(() => { readOnlyDB.close(); });
|
||||||
|
|
||||||
|
t.assert.deepStrictEqual(
|
||||||
|
readOnlyDB.prepare('SELECT * FROM data').all(),
|
||||||
|
[{ __proto__: null, key: 1 }]
|
||||||
|
);
|
||||||
|
t.assert.throws(() => {
|
||||||
|
readOnlyDB.exec('INSERT INTO data (key) VALUES (1);');
|
||||||
|
}, {
|
||||||
|
code: 'ERR_SQLITE_ERROR',
|
||||||
|
message: 'attempt to write a readonly database',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('query params are supported with string', (t) => {
|
||||||
|
const url = pathToFileURL(baseDbPath);
|
||||||
|
url.searchParams.set('mode', 'ro');
|
||||||
|
|
||||||
|
// Ensures a valid URI passed as a string is supported
|
||||||
|
const readOnlyDB = new DatabaseSync(url.toString());
|
||||||
|
t.after(() => { readOnlyDB.close(); });
|
||||||
|
|
||||||
|
t.assert.deepStrictEqual(
|
||||||
|
readOnlyDB.prepare('SELECT * FROM data').all(),
|
||||||
|
[{ __proto__: null, key: 1 }]
|
||||||
|
);
|
||||||
|
t.assert.throws(() => {
|
||||||
|
readOnlyDB.exec('INSERT INTO data (key) VALUES (1);');
|
||||||
|
}, {
|
||||||
|
code: 'ERR_SQLITE_ERROR',
|
||||||
|
message: 'attempt to write a readonly database',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('query params are supported with Buffer', (t) => {
|
||||||
|
const url = pathToFileURL(baseDbPath);
|
||||||
|
url.searchParams.set('mode', 'ro');
|
||||||
|
|
||||||
|
// Ensures a valid URI passed as a Buffer is supported
|
||||||
|
const readOnlyDB = new DatabaseSync(Buffer.from(url.toString()));
|
||||||
|
t.after(() => { readOnlyDB.close(); });
|
||||||
|
|
||||||
|
t.assert.deepStrictEqual(
|
||||||
|
readOnlyDB.prepare('SELECT * FROM data').all(),
|
||||||
|
[{ __proto__: null, key: 1 }]
|
||||||
|
);
|
||||||
|
t.assert.throws(() => {
|
||||||
|
readOnlyDB.exec('INSERT INTO data (key) VALUES (1);');
|
||||||
|
}, {
|
||||||
|
code: 'ERR_SQLITE_ERROR',
|
||||||
|
message: 'attempt to write a readonly database',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user