Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ members = [
"packages/sqlite-web"
]
exclude = [
"lib/rain.math.float"
"lib/rain.math.float",
"vendor/sqlite-wasm-rs"
]
resolver = "2"

Expand Down
3 changes: 3 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
name = "test-wasm";
body = ''
set -euxo pipefail
cd vendor/sqlite-wasm-rs
TZ=America/New_York wasm-pack test --headless --chrome . --lib --no-default-features --features precompiled
cd ../..
cd packages/sqlite-web-core
wasm-pack test --headless --chrome
cd ../..
Expand Down
33 changes: 33 additions & 0 deletions svelte-test/tests/integration/vendor-regressions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import type { SQLiteWasmDatabase } from '@rainlanguage/sqlite-web';
import { cleanupDatabase, createTestDatabase } from '../fixtures/test-helpers.js';

describe('vendored sqlite runtime regressions', () => {
let db: SQLiteWasmDatabase;

beforeEach(async () => {
db = await createTestDatabase();
});

afterEach(async () => {
await cleanupDatabase(db);
});

it('uses the configured local offset and daylight-saving rules', async () => {
const result = await db.query(
`SELECT
datetime(0, 'unixepoch', 'localtime') AS winter_time,
datetime(1593561600, 'unixepoch', 'localtime') AS summer_time`
);
const rows = JSON.parse(result.value || '[]') as Array<{
winter_time: string;
summer_time: string;
}>;

expect(rows).toHaveLength(1);
expect(rows[0]).toEqual({
winter_time: '1969-12-31 19:00:00',
summer_time: '2020-06-30 20:00:00'
});
});
});
3 changes: 3 additions & 0 deletions svelte-test/vitest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export default defineConfig({
},
// Add cross-origin isolation headers for SharedArrayBuffer and OPFS
providerOptions: {
context: {
timezoneId: 'America/New_York'
},
launch: {
args: [
'--enable-features=SharedArrayBuffer',
Expand Down
4 changes: 0 additions & 4 deletions vendor/sqlite-wasm-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ precompiled = []
name = "sqlite_wasm_rs"
path = "src/lib.rs"

[[test]]
name = "main"
path = "tests/main.rs"

[dependencies.fragile]
version = "2.0.0"

Expand Down
2 changes: 2 additions & 0 deletions vendor/sqlite-wasm-rs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ async fn open_db() -> anyhow::Result<()> {
)
};
assert_eq!(ffi::SQLITE_OK, ret);

Ok(())
}
```

Expand Down
62 changes: 57 additions & 5 deletions vendor/sqlite-wasm-rs/src/shim/impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ pub struct tm {
pub tm_zone: *mut std::os::raw::c_char,
}

const INT53_MAX: time_t = 9007199254740992;
const INT53_MIN: time_t = -9007199254740992;
// ECMAScript Date's TimeClip range is +/- 8.64e15 milliseconds. Staying
// within JavaScript's wider safe-integer range is not sufficient because Date
// returns NaN outside this narrower range.
const JS_DATE_MAX_SECONDS: time_t = 8_640_000_000_000;
const JS_DATE_MIN_SECONDS: time_t = -JS_DATE_MAX_SECONDS;

fn yday_from_date(date: &Date) -> u32 {
const MONTH_DAYS_LEAP_CUMULATIVE: [u32; 12] =
Expand All @@ -45,9 +48,13 @@ fn yday_from_date(date: &Date) -> u32 {
/// https://github.com/sqlite/sqlite-wasm/blob/7c1b309c3bd07d8e6d92f82344108cebbd14f161/sqlite-wasm/jswasm/sqlite3-bundler-friendly.mjs#L3404
#[no_mangle]
pub unsafe extern "C" fn rust_sqlite_wasm_shim_localtime_js(t: time_t, tm: *mut tm) {
assert!(!(INT53_MIN..=INT53_MAX).contains(&t), "wrong time range");
assert!(
(JS_DATE_MIN_SECONDS..=JS_DATE_MAX_SECONDS).contains(&t),
"wrong time range"
);

let date = Date::new(&(t * 1000).into());
// Converting i64 directly creates a JavaScript BigInt, which Date rejects.
let date = Date::new(&((t as f64) * 1000.0).into());
(*tm).tm_sec = date.get_seconds() as _;
(*tm).tm_min = date.get_minutes() as _;
(*tm).tm_hour = date.get_hours() as _;
Expand All @@ -66,7 +73,7 @@ pub unsafe extern "C" fn rust_sqlite_wasm_shim_localtime_js(t: time_t, tm: *mut
&& date.get_timezone_offset() == winter_offset.min(summer_offset),
);

(*tm).tm_gmtoff = (date.get_timezone_offset() * 60.0) as _;
(*tm).tm_gmtoff = (-date.get_timezone_offset() * 60.0) as _;
}

/// https://github.com/sqlite/sqlite-wasm/blob/7c1b309c3bd07d8e6d92f82344108cebbd14f161/sqlite-wasm/jswasm/sqlite3-bundler-friendly.mjs#L3460
Expand Down Expand Up @@ -177,3 +184,48 @@ pub unsafe extern "C" fn rust_sqlite_wasm_shim_realloc(ptr: *mut u8, new_size: u

ptr.add(ALIGN)
}

#[cfg(test)]
mod tests {
use super::*;
use wasm_bindgen_test::wasm_bindgen_test;

#[wasm_bindgen_test]
fn localtime_populates_gmt_offset() {
let timestamp = 1_593_561_600;
let mut value: tm = unsafe { std::mem::zeroed() };

unsafe { rust_sqlite_wasm_shim_localtime_js(timestamp, &mut value) };

assert_eq!(
Date::new(&((timestamp as f64) * 1000.0).into()).get_timezone_offset(),
240.0
);
assert_eq!(value.tm_gmtoff, -4 * 60 * 60);
assert_eq!(value.tm_isdst, 1);
}

#[wasm_bindgen_test]
fn localtime_accepts_javascript_date_boundaries() {
for timestamp in [JS_DATE_MIN_SECONDS, JS_DATE_MAX_SECONDS] {
let mut value: tm = unsafe { std::mem::zeroed() };
unsafe { rust_sqlite_wasm_shim_localtime_js(timestamp, &mut value) };
assert!((0..=11).contains(&value.tm_mon));
assert!((1..=31).contains(&value.tm_mday));
}
}

#[wasm_bindgen_test]
#[should_panic(expected = "wrong time range")]
fn localtime_rejects_above_javascript_date_range() {
let mut value: tm = unsafe { std::mem::zeroed() };
unsafe { rust_sqlite_wasm_shim_localtime_js(JS_DATE_MAX_SECONDS + 1, &mut value) };
}

#[wasm_bindgen_test]
#[should_panic(expected = "wrong time range")]
fn localtime_rejects_below_javascript_date_range() {
let mut value: tm = unsafe { std::mem::zeroed() };
unsafe { rust_sqlite_wasm_shim_localtime_js(JS_DATE_MIN_SECONDS - 1, &mut value) };
}
}
3 changes: 2 additions & 1 deletion vendor/sqlite-wasm-rs/src/shim/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ pub mod export {
// Some sqlite types copied from libsqlite3-sys
pub use super::libsqlite3::*;
pub use super::vfs::sahpool::{
install_opfs_sahpool, OpfsSAHError, OpfsSAHPoolCfg, OpfsSAHPoolCfgBuilder, OpfsSAHPoolUtil,
install_opfs_sahpool, OpfsSAHError, OpfsSAHPoolCfg, OpfsSAHPoolCfgBuilder,
OpfsSAHPoolImport, OpfsSAHPoolUtil,
};

#[cfg(feature = "custom-libc")]
Expand Down
45 changes: 38 additions & 7 deletions vendor/sqlite-wasm-rs/src/shim/vfs/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ unsafe extern "C" fn xRandomness(
zOut: *mut ::std::os::raw::c_char,
) -> ::std::os::raw::c_int {
for i in 0..nByte {
*zOut.offset(i as isize) = (Math::random() * 255000.0) as _;
*zOut.offset(i as isize) = (Math::random() * 256.0) as u8 as _;
}
nByte
}
Expand Down Expand Up @@ -94,23 +94,44 @@ unsafe extern "C" fn xOpen(
flags: ::std::os::raw::c_int,
pOutFlags: *mut ::std::os::raw::c_int,
) -> ::std::os::raw::c_int {
let Ok(s) = CStr::from_ptr(zName).to_str() else {
return SQLITE_ERROR;
let requested_name = if zName.is_null() {
None
} else {
let Ok(name) = CStr::from_ptr(zName).to_str() else {
return SQLITE_ERROR;
};
Some(name.to_owned())
};

let mut name2file = name2file();
let mem_file = if let Some(mem_file) = name2file.get(s) {
let (name, flags) = match requested_name {
Some(name) => (name, flags),
None => {
let name = loop {
let candidate = format!(
":sqlite-temp:{:08x}",
(Math::random() * (u32::MAX as f64 + 1.0)) as u32
);
if !name2file.contains_key(&candidate) {
break candidate;
}
};
(name, flags | SQLITE_OPEN_CREATE | SQLITE_OPEN_DELETEONCLOSE)
}
};

let mem_file = if let Some(mem_file) = name2file.get(&name) {
Arc::clone(mem_file)
} else {
if flags & SQLITE_OPEN_CREATE == 0 {
return SQLITE_CANTOPEN;
}
let file = Arc::new(RwLock::new(MemFile {
name: s.into(),
name: name.clone(),
flags,
data: Vec::new(),
}));
name2file.insert(s.into(), Arc::clone(&file));
name2file.insert(name, Arc::clone(&file));
file
};

Expand Down Expand Up @@ -156,7 +177,17 @@ unsafe extern "C" fn xFullPathname(
nOut: ::std::os::raw::c_int,
zOut: *mut ::std::os::raw::c_char,
) -> ::std::os::raw::c_int {
zName.copy_to(zOut, nOut as usize);
if zName.is_null() || zOut.is_null() || nOut <= 0 {
return SQLITE_CANTOPEN;
}
let bytes = CStr::from_ptr(zName).to_bytes_with_nul();
if bytes.len() > nOut as usize {
return SQLITE_CANTOPEN;
}
bytes
.as_ptr()
.cast::<::std::os::raw::c_char>()
.copy_to_nonoverlapping(zOut, bytes.len());
SQLITE_OK
}

Expand Down
Loading
Loading