// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use deno_core::error::AnyError; use deno_graph::FastCheckCacheItem; use deno_graph::FastCheckCacheKey; use deno_runtime::deno_webstorage::rusqlite::params; use super::cache_db::CacheDB; use super::cache_db::CacheDBConfiguration; use super::cache_db::CacheFailure; pub static FAST_CHECK_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration { table_initializer: "CREATE TABLE IF NOT EXISTS fastcheckcache ( hash TEXT PRIMARY KEY, data TEXT NOT NULL );", on_version_change: "DELETE FROM fastcheckcache;", preheat_queries: &[], on_failure: CacheFailure::Blackhole, }; #[derive(Clone)] pub struct FastCheckCache { inner: FastCheckCacheInner, } impl FastCheckCache { pub fn new(db: CacheDB) -> Self { Self { inner: FastCheckCacheInner::new(db), } } fn ensure_ok(res: Result) -> T { match res { Ok(x) => x, Err(err) => { // TODO(mmastrac): This behavior was inherited from before the refactoring but it probably makes sense to move it into the cache // at some point. // should never error here, but if it ever does don't fail if cfg!(debug_assertions) { panic!("Error using fast check cache: {err:#}"); } else { log::debug!("Error using fast check cache: {:#}", err); } T::default() } } } } impl deno_graph::FastCheckCache for FastCheckCache { fn get(&self, key: FastCheckCacheKey) -> Option { Self::ensure_ok(self.inner.get(key)) } fn set(&self, key: FastCheckCacheKey, value: FastCheckCacheItem) { Self::ensure_ok(self.inner.set(key, &value)); } } #[derive(Clone)] struct FastCheckCacheInner { conn: CacheDB, } impl FastCheckCacheInner { pub fn new(conn: CacheDB) -> Self { Self { conn } } pub fn get( &self, key: FastCheckCacheKey, ) -> Result, AnyError> { let query = " SELECT data FROM fastcheckcache WHERE hash=?1 LIMIT 1"; let res = self .conn // key is a string because SQLite can't handle u64 .query_row(query, params![key.as_u64().to_string()], |row| { let value: Vec = row.get(0)?; Ok(bincode::deserialize::(&value)?) })?; Ok(res) } pub fn set( &self, key: FastCheckCacheKey, data: &FastCheckCacheItem, ) -> Result<(), AnyError> { let sql = " INSERT OR REPLACE INTO fastcheckcache (hash, data) VALUES (?1, ?2)"; self.conn.execute( sql, params![key.as_u64().to_string(), &bincode::serialize(data)?], )?; Ok(()) } } #[cfg(test)] mod test { use std::collections::BTreeSet; use deno_ast::ModuleSpecifier; use deno_graph::FastCheckCacheModuleItem; use deno_graph::FastCheckCacheModuleItemDiagnostic; use deno_semver::package::PackageNv; use super::*; #[test] pub fn cache_general_use() { let conn = CacheDB::in_memory(&FAST_CHECK_CACHE_DB, "1.0.0"); let cache = FastCheckCacheInner::new(conn); let key = FastCheckCacheKey::build( &PackageNv::from_str("@scope/a@1.0.0").unwrap(), &Default::default(), ); assert!(cache.get(key).unwrap().is_none()); let value = FastCheckCacheItem { dependencies: BTreeSet::from([ PackageNv::from_str("@scope/b@1.0.0").unwrap() ]), modules: vec![( ModuleSpecifier::parse("https://jsr.io/test.ts").unwrap(), FastCheckCacheModuleItem::Diagnostic( FastCheckCacheModuleItemDiagnostic { source_hash: 123 }, ), )], }; cache.set(key, &value).unwrap(); let stored_value = cache.get(key).unwrap().unwrap(); assert_eq!(stored_value, value); // adding when already exists should not cause issue cache.set(key, &value).unwrap(); // recreating with same cli version should still have it let conn = cache.conn.recreate_with_version("1.0.0"); let cache = FastCheckCacheInner::new(conn); let stored_value = cache.get(key).unwrap().unwrap(); assert_eq!(stored_value, value); // now changing the cli version should clear it let conn = cache.conn.recreate_with_version("2.0.0"); let cache = FastCheckCacheInner::new(conn); assert!(cache.get(key).unwrap().is_none()); } }