fix(ext/node): fix sqlite extension used for testing; ensure related tests are actually meaningful (#31455)

The SQLite extension-loading tests, prior to this PR, were
short-circuiting due to an incorrect search path, and not actually
verifying that extensions loaded. When this invalid path was fixed, the
result was a segfault due to an error in the extension itself (failing
to use the pointer provided at initialization time to access the
existing sqlite instance).

This is addressed in three different stages:

1. Switch to using `ignore` rather than an early return when tests
cannot be run due to the extension not existing. *Running
`sqlite_extension_test.ts` in this state allows the original problem, of
tests not actually being meaningfully run, to be observed.*
2. Fix the location used to run sqlite extension tests. *This fixes the
problem exposed by the above step, but causes a segmentation fault at
extension load time.*
3. Split extension build into a separate module, written to use
rusqlite's `loadable_extension` support. *This fixes the segfault
exposed above.*

Because the sysroot used for x86_64 Linux builds does not provide
sqlite, and rusqlite's extension build mechanism attempts to link
against libsqlite3.so regardless of whether the `bundled` flag is used,
this PR does not use the sysroot for the extension build.

Fixes #31454
This commit is contained in:
Charles Duffy 2025-12-08 04:30:54 -06:00 committed by GitHub
parent b8b549d574
commit 40cf41275b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 423 additions and 93 deletions

15
Cargo.lock generated
View file

@ -8527,6 +8527,13 @@ dependencies = [
"winnow 0.6.26",
]
[[package]]
name = "sqlite_extension_test"
version = "0.1.0"
dependencies = [
"test_server",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@ -9346,14 +9353,6 @@ dependencies = [
"zip",
]
[[package]]
name = "test_sqlite_extension"
version = "0.1.0"
dependencies = [
"rusqlite",
"test_server",
]
[[package]]
name = "text-size"
version = "1.1.1"

View file

@ -51,7 +51,9 @@ members = [
"tests/sqlite_extension_test",
"tests/util/server",
]
exclude = ["tests/util/std/hash/_wasm"]
# tests/sqlite_extension is excluded because it requires rusqlite's "loadable_extension"
# feature, which is incompatible with the "session" feature used by the rest of the workspace.
exclude = ["tests/util/std/hash/_wasm", "tests/sqlite_extension"]
[workspace.package]
authors = ["the Deno authors"]

142
tests/sqlite_extension/Cargo.lock generated Normal file
View file

@ -0,0 +1,142 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown",
]
[[package]]
name = "libsqlite3-sys"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
dependencies = [
"pkg-config",
"prettyplease",
"quote",
"syn",
"vcpkg",
]
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rusqlite"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "syn"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "test_sqlite_extension"
version = "0.1.0"
dependencies = [
"rusqlite",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"

View file

@ -0,0 +1,23 @@
# Copyright 2018-2025 the Deno authors. MIT license.
# This crate is excluded from the workspace because it requires rusqlite's
# "loadable_extension" feature, which is incompatible with the "session" feature
# used by the rest of the workspace. Build with:
# cargo build --manifest-path tests/sqlite_extension/Cargo.toml
[package]
name = "test_sqlite_extension"
version = "0.1.0"
authors = ["the Deno authors"]
edition = "2024"
license = "MIT"
publish = false
repository = "https://github.com/denoland/deno"
[lib]
crate-type = ["cdylib"]
[workspace]
[dependencies]
rusqlite = { version = "0.37.0", default-features = false, features = ["loadable_extension", "functions"] }

View file

@ -0,0 +1,42 @@
// Copyright 2018-2025 the Deno authors. MIT license.
//! A simple SQLite loadable extension for testing.
//!
//! This extension provides a `test_func(x)` function that returns its argument unchanged.
use std::os::raw::c_char;
use std::os::raw::c_int;
use rusqlite::Connection;
use rusqlite::Result;
use rusqlite::ffi;
use rusqlite::functions::FunctionFlags;
use rusqlite::types::ToSqlOutput;
use rusqlite::types::Value;
/// Entry point for SQLite to load the extension.
#[expect(clippy::not_unsafe_ptr_arg_deref)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn sqlite3_extension_init(
db: *mut ffi::sqlite3,
pz_err_msg: *mut *mut c_char,
p_api: *mut ffi::sqlite3_api_routines,
) -> c_int {
// SAFETY: This function is called by SQLite with valid pointers.
// extension_init2 handles the API initialization.
unsafe { Connection::extension_init2(db, pz_err_msg, p_api, extension_init) }
}
fn extension_init(db: Connection) -> Result<bool> {
db.create_scalar_function(
"test_func",
1,
FunctionFlags::SQLITE_DETERMINISTIC,
|ctx| {
// Return the argument value unchanged
let value: Value = ctx.get(0)?;
Ok(ToSqlOutput::Owned(value))
},
)?;
Ok(false)
}

165
tests/sqlite_extension_test/Cargo.lock generated Normal file
View file

@ -0,0 +1,165 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "cc"
version = "1.2.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "find-msvc-tools"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown",
]
[[package]]
name = "libsqlite3-sys"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
dependencies = [
"cc",
"pkg-config",
"prettyplease",
"quote",
"syn",
"vcpkg",
]
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rusqlite"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "syn"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "test_sqlite_extension"
version = "0.1.0"
dependencies = [
"rusqlite",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"

View file

@ -1,7 +1,7 @@
# Copyright 2018-2025 the Deno authors. MIT license.
[package]
name = "test_sqlite_extension"
name = "sqlite_extension_test"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
@ -9,11 +9,9 @@ license.workspace = true
publish = false
repository.workspace = true
[lib]
crate-type = ["cdylib"]
[dependencies]
rusqlite.workspace = true
[[test]]
name = "integration_tests"
path = "tests/integration_tests.rs"
[dev-dependencies]
test_util.workspace = true

View file

@ -1,4 +1,9 @@
// Copyright 2018-2025 the Deno authors. MIT license.
// NOTE: This test requires building the extension separately (it is excluded
// from the workspace due to incompatible rusqlite feature requirements):
// cargo build --manifest-path tests/sqlite_extension/Cargo.toml
import { DatabaseSync } from "node:sqlite";
import { assertEquals, assertThrows } from "@std/assert";
import * as path from "node:path";
@ -9,7 +14,7 @@ const extensionPath = (() => {
const isLinux = Deno.build.os === "linux";
const currentDir = new URL(".", import.meta.url).pathname;
const denoDir = path.resolve(currentDir, "..");
const denoDir = path.resolve(currentDir, "../..");
let libPrefix = "";
let libSuffix = "";
@ -30,17 +35,20 @@ const extensionPath = (() => {
return path.join(targetDir, `${libPrefix}test_sqlite_extension.${libSuffix}`);
})();
const extensionExists = (() => {
try {
Deno.statSync(extensionPath);
return true;
} catch {
return false;
}
})();
Deno.test({
name: "[node/sqlite] DatabaseSync loadExtension",
ignore: !extensionExists,
permissions: { read: true, write: true, ffi: true },
fn() {
// skip the test if the extension is not found
try {
Deno.statSync(extensionPath);
} catch {
return;
}
const db = new DatabaseSync(":memory:", {
allowExtension: true,
readOnly: false,

View file

@ -1,67 +0,0 @@
// Copyright 2018-2025 the Deno authors. MIT license.
#![allow(clippy::undocumented_unsafe_blocks)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::macro_metavars_in_unsafe)]
use std::os::raw::c_char;
use std::os::raw::c_int;
use rusqlite::ffi::*;
#[unsafe(no_mangle)]
#[allow(non_upper_case_globals)]
pub static mut sqlite3_api: *const sqlite3_api_routines = std::ptr::null();
#[macro_export]
macro_rules! SQLITE3_EXTENSION_INIT2 {
($api:expr) => {
#[allow(unused_unsafe)]
unsafe {
sqlite3_api = $api;
}
};
}
unsafe extern "C" fn test_func(
context: *mut sqlite3_context,
argc: c_int,
argv: *mut *mut sqlite3_value,
) {
unsafe {
if argc != 1 {
sqlite3_result_error(
context,
c"test_func() requires exactly 1 argument".as_ptr() as *const c_char,
-1,
);
return;
}
let arg = *argv;
sqlite3_result_value(context, arg);
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn sqlite3_extension_init(
db: *mut sqlite3,
_pz_err_msg: *mut *mut c_char,
p_api: *const sqlite3_api_routines,
) -> c_int {
unsafe {
SQLITE3_EXTENSION_INIT2!(p_api);
sqlite3_create_function_v2(
db,
c"test_func".as_ptr() as *const c_char,
1,
SQLITE_UTF8 | SQLITE_DETERMINISTIC,
std::ptr::null_mut(),
Some(test_func),
None,
None,
None,
)
}
}

View file

@ -16,11 +16,26 @@ const BUILD_VARIANT: &str = "debug";
const BUILD_VARIANT: &str = "release";
fn build_extension() {
// The extension is in a separate standalone package (excluded from workspace)
// because it requires rusqlite's "loadable_extension" feature which is
// incompatible with the "session" feature used by the rest of the workspace.
let tests_dir = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
let extension_manifest =
tests_dir.join("sqlite_extension").join("Cargo.toml");
// Output to the repo's target directory so the Deno tests can find it
let target_dir = tests_dir.parent().unwrap().join("target");
let mut build_plugin_base = Command::new("cargo");
let mut build_plugin = build_plugin_base
.arg("build")
.arg("-p")
.arg("test_sqlite_extension");
.arg("--manifest-path")
.arg(&extension_manifest)
.arg("--target-dir")
.arg(&target_dir)
// Don't inherit RUSTFLAGS from the test environment - the sysroot
// configuration used for main Deno builds doesn't have libsqlite3
.env_remove("RUSTFLAGS")
.env_remove("RUSTDOCFLAGS");
if BUILD_VARIANT == "release" {
build_plugin = build_plugin.arg("--release");
@ -35,7 +50,10 @@ fn build_extension() {
"cargo build error: {}",
String::from_utf8_lossy(&build_plugin_output.stderr)
);
assert!(build_plugin_output.status.success());
assert!(
build_plugin_output.status.success(),
"Extension build failed. Check that rusqlite features are compatible."
);
}
#[test]