Skip to content

Commit f93872a

Browse files
tniessentpoisseau
authored andcommitted
sqlite: disable DQS misfeature by default
Double-quoted string (DQS) literals are not allowed by the SQL standard, which defines that text enclosed in double quotes is to be interpreted as an identifier only and never as a string literal. Nevertheless, for historical reasons, SQLite allows double-quoted string literals in some cases, which leads to inconsistent behavior and subtle bugs. This commit changes the behavior of the built-in Node.js API for SQLite such that the DQS misfeature is disabled by default. This is recommended by the developers of SQLite. Users can explicitly enable DQS for compatibility with legacy database schemas if necessary. PR-URL: nodejs#55297 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Jake Yuesong Li <jake.yuesong@gmail.com>
1 parent 29d30b8 commit f93872a

4 files changed

Lines changed: 75 additions & 4 deletions

File tree

doc/api/sqlite.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ added: v22.5.0
112112
legacy database schemas. The enforcement of foreign key constraints can be
113113
enabled and disabled after opening the database using
114114
[`PRAGMA foreign_keys`][]. **Default:** `true`.
115+
* `enableDoubleQuotedStringLiterals` {boolean} If `true`, SQLite will accept
116+
[double-quoted string literals][]. This is not recommended but can be
117+
enabled for compatibility with legacy database schemas.
118+
**Default:** `false`.
115119

116120
Constructs a new `DatabaseSync` instance.
117121

@@ -351,6 +355,7 @@ exception.
351355
[`sqlite3_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html
352356
[connection]: https://www.sqlite.org/c3ref/sqlite3.html
353357
[data types]: https://www.sqlite.org/datatype3.html
358+
[double-quoted string literals]: https://www.sqlite.org/quirks.html#dblquote
354359
[in memory]: https://www.sqlite.org/inmemorydb.html
355360
[parameters are bound]: https://www.sqlite.org/c3ref/bind_blob.html
356361
[prepared statement]: https://www.sqlite.org/c3ref/stmt.html

src/node_sqlite.cc

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,15 @@ DatabaseSync::DatabaseSync(Environment* env,
9898
Local<Object> object,
9999
Local<String> location,
100100
bool open,
101-
bool enable_foreign_keys_on_open)
101+
bool enable_foreign_keys_on_open,
102+
bool enable_dqs_on_open)
102103
: BaseObject(env, object) {
103104
MakeWeak();
104105
node::Utf8Value utf8_location(env->isolate(), location);
105106
location_ = utf8_location.ToString();
106107
connection_ = nullptr;
107108
enable_foreign_keys_on_open_ = enable_foreign_keys_on_open;
109+
enable_dqs_on_open_ = enable_dqs_on_open;
108110

109111
if (open) {
110112
Open();
@@ -134,6 +136,17 @@ bool DatabaseSync::Open() {
134136
int r = sqlite3_open_v2(location_.c_str(), &connection_, flags, nullptr);
135137
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
136138

139+
r = sqlite3_db_config(connection_,
140+
SQLITE_DBCONFIG_DQS_DML,
141+
static_cast<int>(enable_dqs_on_open_),
142+
nullptr);
143+
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
144+
r = sqlite3_db_config(connection_,
145+
SQLITE_DBCONFIG_DQS_DDL,
146+
static_cast<int>(enable_dqs_on_open_),
147+
nullptr);
148+
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
149+
137150
int foreign_keys_enabled;
138151
r = sqlite3_db_config(connection_,
139152
SQLITE_DBCONFIG_ENABLE_FKEY,
@@ -184,6 +197,7 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
184197

185198
bool open = true;
186199
bool enable_foreign_keys = true;
200+
bool enable_dqs = false;
187201

188202
if (args.Length() > 1) {
189203
if (!args[1]->IsObject()) {
@@ -224,10 +238,32 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
224238
}
225239
enable_foreign_keys = enable_foreign_keys_v.As<Boolean>()->Value();
226240
}
241+
242+
Local<String> enable_dqs_string = FIXED_ONE_BYTE_STRING(
243+
env->isolate(), "enableDoubleQuotedStringLiterals");
244+
Local<Value> enable_dqs_v;
245+
if (!options->Get(env->context(), enable_dqs_string)
246+
.ToLocal(&enable_dqs_v)) {
247+
return;
248+
}
249+
if (!enable_dqs_v->IsUndefined()) {
250+
if (!enable_dqs_v->IsBoolean()) {
251+
node::THROW_ERR_INVALID_ARG_TYPE(
252+
env->isolate(),
253+
"The \"options.enableDoubleQuotedStringLiterals\" argument must be "
254+
"a boolean.");
255+
return;
256+
}
257+
enable_dqs = enable_dqs_v.As<Boolean>()->Value();
258+
}
227259
}
228260

229-
new DatabaseSync(
230-
env, args.This(), args[0].As<String>(), open, enable_foreign_keys);
261+
new DatabaseSync(env,
262+
args.This(),
263+
args[0].As<String>(),
264+
open,
265+
enable_foreign_keys,
266+
enable_dqs);
231267
}
232268

233269
void DatabaseSync::Open(const FunctionCallbackInfo<Value>& args) {

src/node_sqlite.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class DatabaseSync : public BaseObject {
2222
v8::Local<v8::Object> object,
2323
v8::Local<v8::String> location,
2424
bool open,
25-
bool enable_foreign_keys_on_open);
25+
bool enable_foreign_keys_on_open,
26+
bool enable_dqs_on_open);
2627
void MemoryInfo(MemoryTracker* tracker) const override;
2728
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
2829
static void Open(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -45,6 +46,7 @@ class DatabaseSync : public BaseObject {
4546
sqlite3* connection_;
4647
std::unordered_set<StatementSync*> statements_;
4748
bool enable_foreign_keys_on_open_;
49+
bool enable_dqs_on_open_;
4850
};
4951

5052
class StatementSync : public BaseObject {

test/parallel/test-sqlite-database-sync.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,34 @@ suite('DatabaseSync() constructor', () => {
8686
t.after(() => { db.close(); });
8787
db.exec('INSERT INTO bar (foo_id) VALUES (1)');
8888
});
89+
90+
test('throws if options.enableDoubleQuotedStringLiterals is provided but is not a boolean', (t) => {
91+
t.assert.throws(() => {
92+
new DatabaseSync('foo', { enableDoubleQuotedStringLiterals: 5 });
93+
}, {
94+
code: 'ERR_INVALID_ARG_TYPE',
95+
message: /The "options\.enableDoubleQuotedStringLiterals" argument must be a boolean/,
96+
});
97+
});
98+
99+
test('disables double-quoted string literals by default', (t) => {
100+
const dbPath = nextDb();
101+
const db = new DatabaseSync(dbPath);
102+
t.after(() => { db.close(); });
103+
t.assert.throws(() => {
104+
db.exec('SELECT "foo";');
105+
}, {
106+
code: 'ERR_SQLITE_ERROR',
107+
message: /no such column: "foo"/,
108+
});
109+
});
110+
111+
test('allows enabling double-quoted string literals', (t) => {
112+
const dbPath = nextDb();
113+
const db = new DatabaseSync(dbPath, { enableDoubleQuotedStringLiterals: true });
114+
t.after(() => { db.close(); });
115+
db.exec('SELECT "foo";');
116+
});
89117
});
90118

91119
suite('DatabaseSync.prototype.open()', () => {

0 commit comments

Comments
 (0)