This commit is contained in:
Ibraheem Ahmed 2025-08-21 19:44:48 -04:00
parent 2bb67d76b3
commit cb8192b82e
11 changed files with 227 additions and 331 deletions

View file

@ -38,7 +38,6 @@ compact_str = { version = "0.9", optional = true }
shuttle = { version = "0.8.1", optional = true }
# Persistent caching
erased-serde = { version = "0.4.6", optional = true }
serde = { version = "1.0.219", features = ["derive"], optional = true }
[features]
@ -46,7 +45,6 @@ default = ["salsa_unstable", "rayon", "macros", "inventory", "accumulator", "per
inventory = ["dep:inventory"]
persistence = [
"dep:serde",
"dep:erased-serde",
"salsa-macros/persistence",
"thin-vec/serde",
]

View file

@ -201,7 +201,12 @@ macro_rules! setup_interned_struct {
impl $Configuration {
pub fn ingredient(zalsa: &$zalsa::Zalsa) -> &$zalsa_struct::IngredientImpl<Self> {
pub fn ingredient(db: &dyn $zalsa::Database) -> &$zalsa_struct::IngredientImpl<Self> {
Self::ingredient_(db.zalsa())
}
#[inline]
fn ingredient_(zalsa: &$zalsa::Zalsa) -> &$zalsa_struct::IngredientImpl<Self> {
static CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Configuration>> =
$zalsa::IngredientCache::new();
@ -250,7 +255,7 @@ macro_rules! setup_interned_struct {
zalsa: &$zalsa::Zalsa
) -> impl Iterator<Item = $zalsa::DatabaseKeyIndex> + '_ {
let ingredient_index = zalsa.lookup_jar_by_type::<$zalsa_struct::JarImpl<$Configuration>>();
<$Configuration>::ingredient(zalsa).entries(zalsa).map(|(key, _)| key)
<$Configuration>::ingredient_(zalsa).entries(zalsa).map(|(key, _)| key)
}
#[inline]
@ -316,7 +321,7 @@ macro_rules! setup_interned_struct {
)*
{
let (zalsa, zalsa_local) = db.zalsas();
$Configuration::ingredient(zalsa).intern(zalsa, zalsa_local,
$Configuration::ingredient_(zalsa).intern(zalsa, zalsa_local,
StructKey::<$db_lt>($($field_id,)* std::marker::PhantomData::default()), |_, data| ($($zalsa::interned::Lookup::into_owned(data.$field_index),)*))
}
@ -328,7 +333,7 @@ macro_rules! setup_interned_struct {
$Db: ?Sized + $zalsa::Database,
{
let zalsa = db.zalsa();
let fields = $Configuration::ingredient(zalsa).fields(zalsa, self);
let fields = $Configuration::ingredient_(zalsa).fields(zalsa, self);
$zalsa::return_mode_expression!(
$field_option,
$field_ty,
@ -351,7 +356,7 @@ macro_rules! setup_interned_struct {
{
$zalsa::with_attached_database(|db| {
let zalsa = db.zalsa();
let fields = $Configuration::ingredient(zalsa).fields(zalsa, this);
let fields = $Configuration::ingredient_(zalsa).fields(zalsa, this);
let mut f = f.debug_struct(stringify!($Struct));
$(
let f = f.field(stringify!($field_id), &fields.$field_index);
@ -375,7 +380,7 @@ macro_rules! setup_interned_struct {
{
$zalsa::with_attached_database(|db| {
let zalsa = db.zalsa();
let fields = $Configuration::ingredient(zalsa).fields(zalsa, this);
let fields = $Configuration::ingredient_(zalsa).fields(zalsa, this);
let mut f = f.debug_struct(stringify!($Struct));
$(
let f = f.field(stringify!($field_id), &fields.$field_index);

View file

@ -105,7 +105,7 @@ macro_rules! setup_tracked_fn {
type Ingredient = $zalsa::function::IngredientImpl<$Configuration>;
fn ingredient(db: &dyn $zalsa::Database) -> &Self::Ingredient {
$Configuration::fn_ingredient(db)
$Configuration::uninit_fn_ingredient(db.zalsa())
}
}
@ -233,12 +233,17 @@ macro_rules! setup_tracked_fn {
#[inline]
fn fn_ingredient_<'z>(db: &dyn $Db, zalsa: &'z $zalsa::Zalsa) -> &'z $zalsa::function::IngredientImpl<$Configuration> {
Self::uninit_fn_ingredient(zalsa)
.get_or_init(|| *<dyn $Db as $Db>::zalsa_register_downcaster(db))
}
#[inline]
fn uninit_fn_ingredient(zalsa: &$zalsa::Zalsa) -> &$zalsa::function::IngredientImpl<$Configuration> {
// SAFETY: `lookup_jar_by_type` returns a valid ingredient index, and the first
// ingredient created by our jar is the function ingredient.
unsafe {
$FN_CACHE.get_or_create(zalsa, || zalsa.lookup_jar_by_type::<$fn_name>())
}
.get_or_init(|| *<dyn $Db as $Db>::zalsa_register_downcaster(db))
}
pub fn fn_ingredient_mut(db: &mut dyn $Db) -> &mut $zalsa::function::IngredientImpl<Self> {

View file

@ -152,72 +152,41 @@ pub fn current_revision<Db: ?Sized + Database>(db: &Db) -> Revision {
db.zalsa().current_revision()
}
#[cfg(feature = "persistence")]
pub use persistence::SerializeBuilder;
#[cfg(feature = "persistence")]
pub(crate) mod persistence {
use crate::plumbing::{Ingredient, Persistable};
use crate::zalsa::{HasJar, Zalsa};
use crate::{Database, IngredientIndex};
use crate::plumbing::Ingredient;
use crate::zalsa::Zalsa;
use crate::{Database, HasJar};
use std::fmt;
use serde::de::{self, DeserializeSeed, SeqAccess};
use serde::de::DeserializeSeed;
use serde::Deserializer;
impl dyn Database {
/// Returns a type implementing [`serde::Serialize`], that can be used to serialize the
/// current state of the database.
pub fn as_serialize<'db, I>(
&'db mut self,
ingredients: impl Fn(SerializeBuilder<'db>) -> I,
) -> impl serde::Serialize + '_
where
I: serde::Serialize + 'db,
{
(self.zalsa().runtime(), ingredients(SerializeBuilder(self)))
pub fn as_serialize<'db>(&self) -> impl serde::Serialize + '_ {
self.zalsa().runtime()
}
/// Deserialize the database using a [`serde::Deserializer`].
///
/// This method will modify the database in-place based on the serialized data.
pub fn deserialize<'db, D>(&mut self, deserializer: D) -> Result<(), D::Error>
where
D: serde::Deserializer<'db>,
{
DeserializeDatabase(self.zalsa_mut()).deserialize(deserializer)
}
}
// TODO: ingredient_set! { Interned<'_>, Tracked<'_>, query, ... }
// TODO: macro_rules! ingredient_set
pub struct SerializeBuilder<'db>(&'db dyn Database);
impl<'db> SerializeBuilder<'db> {
pub fn ingredient<I>(&self) -> impl serde::Serialize + 'db
where
I: HasJar,
I::Ingredient: Persistable,
{
// SAFETY: `Database::as_serialize` captures a mutable reference to the database.
unsafe { I::ingredient(self.0).as_serialize(self.0.zalsa()) }
/// Returns a type implementing [`DeserializeSeed`] that can be used to deserialize
/// the database in-place.
pub fn as_deserialize(&mut self) -> impl for<'de> DeserializeSeed<'de> + '_ {
DeserializeDatabase(self.zalsa_mut())
}
}
pub struct DeserializeBuilder<'db> {
db: &'db dyn Database,
ingredients: Vec<fn()>
}
struct DeserializeDatabase<'db>(&'db mut Zalsa);
impl<'db> SerializeBuilder<'db> {
pub fn ingredient<I>(&self) -> impl serde::Serialize + 'db
impl<'de> DeserializeSeed<'de> for DeserializeDatabase<'_> {
type Value = ();
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
I: HasJar,
I::Ingredient: Persistable,
D: Deserializer<'de>,
{
// SAFETY: `Database::as_serialize` captures a mutable reference to the database.
unsafe { I::ingredient(self.0).as_serialize(self.0.zalsa()) }
let mut runtime = <crate::Runtime as serde::Deserialize>::deserialize(deserializer)?;
self.0.runtime_mut().deserialize_from(&mut runtime);
Ok(())
}
}
@ -226,97 +195,25 @@ pub(crate) mod persistence {
///
/// This method will temporarily remove the ingredient from the database, so any attempts
/// to lookup the ingredient will fail.
pub(crate) fn with_mut_ingredient<I, R>(
ingredient: &I,
zalsa: &mut Zalsa,
f: impl FnOnce(&mut I, &mut Zalsa) -> R,
pub fn with_mut_ingredient<I, R>(
db: &mut dyn crate::Database,
f: impl FnOnce(&mut I::Ingredient, &mut dyn crate::Database) -> R,
) -> R
where
I: Ingredient,
I: HasJar,
{
let index = ingredient.ingredient_index();
let index = I::ingredient(db).ingredient_index();
// Remove the ingredient temporarily, to avoid holding overlapping mutable borrows.
let mut ingredient = zalsa.take_ingredient(index);
let mut ingredient = db.zalsa_mut().take_ingredient(index);
// Call the function with the concrete ingredient.
let ingredient_mut = <dyn std::any::Any>::downcast_mut(&mut *ingredient).unwrap();
let value = f(ingredient_mut, zalsa);
let value = f(ingredient_mut, db);
zalsa.replace_ingredient(index, ingredient);
db.zalsa_mut().replace_ingredient(index, ingredient);
value
}
#[derive(serde::Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum DatabaseField {
Runtime,
Ingredients,
}
struct DeserializeDatabase<'db>(&'db mut Zalsa);
impl<'de> de::DeserializeSeed<'de> for DeserializeDatabase<'_> {
type Value = ();
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_seq(self)
}
}
impl<'de> serde::de::Visitor<'de> for DeserializeDatabase<'_> {
type Value = ();
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct Database")
}
fn visit_seq<V>(self, mut seq: V) -> Result<(), V::Error>
where
V: SeqAccess<'de>,
{
let mut runtime = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
let () = seq
.next_element_seed(DeserializeIngredients(self.0))?
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
self.0.runtime_mut().deserialize_from(&mut runtime);
Ok(())
}
}
struct DeserializeIngredients<'db>(&'db mut Zalsa);
impl<'de> serde::de::DeserializeSeed<'de> for DeserializeIngredients<'_> {
type Value = ();
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_seq(self)
}
}
impl<'de> serde::de::Visitor<'de> for DeserializeIngredients<'_> {
type Value = ();
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
while let Some(x) = seq.next_element_seed(seed) {}
}
}
}
#[cfg(feature = "salsa_unstable")]

View file

@ -264,31 +264,25 @@ where
}
#[cfg(feature = "persistence")]
pub unsafe fn as_serialize<'db, S>(
pub fn as_serialize<'db>(
&'db self,
zalsa: &'db Zalsa,
db: &'db dyn crate::Database,
) -> impl serde::Serialize + 'db {
persistence::SerializeIngredient {
zalsa,
ingredient: self,
zalsa: db.zalsa(),
}
}
#[cfg(feature = "persistence")]
pub fn deserialize<'de, D>(
&mut self,
zalsa: &mut Zalsa,
deserializer: D,
) -> Result<(), D::Error>
where
D: serde::Deserializer<'de>,
{
let deserialize = persistence::DeserializeIngredient {
zalsa,
pub fn as_deserialize<'db>(
&'db mut self,
db: &'db mut dyn crate::Database,
) -> impl for<'de> serde::de::DeserializeSeed<'de, Value = ()> + 'db {
persistence::DeserializeIngredient {
ingredient: self,
};
serde::de::DeserializeSeed::deserialize(deserialize, deserializer)
zalsa: db.zalsa_mut(),
}
}
}

View file

@ -218,53 +218,6 @@ pub trait Ingredient: Any + std::fmt::Debug + Send + Sync {
}
}
#[cfg(feature = "persistence")]
pub trait Persistable: Ingredient {
/// Returns a type implementing [`serde::Serialize`], that can be used to serialize the
/// current state of the ingredient.
///
/// # Safety
///
/// While this method takes an immutable reference to the database, it can only be called when a
/// the serializer has exclusive access to the database.
unsafe fn as_serialize<'db>(&self, zalsa: &'db Zalsa) -> impl serde::Serialize + 'db;
/// Deserialize the ingredient using a [`serde::Deserializer`].
///
/// This method will modify the database in-place based on the serialized data.
fn deserialize<'de, D>(&self, zalsa: &mut Zalsa, deserializer: D) -> Result<(), D::Error>
where
D: serde::Deserializer<'de>;
}
trait X: Any {
fn bar(&self) -> usize;
fn foo(&self, x: &dyn Any) -> usize
where
Self: Sized,
{
x.downcast_ref::<Self>().unwrap().bar()
}
}
trait Y: Any {
fn bar(&self) -> usize;
fn foo(&self, y: &dyn Any) -> usize;
}
struct Bar;
impl Y for Bar {
fn bar(&self) -> usize {
0
}
fn foo(&self, y: &dyn Any) -> usize {
y.downcast_ref::<Self>().unwrap().bar()
}
}
impl dyn Ingredient {
/// Equivalent to the `downcast` method on `Any`.
///

View file

@ -257,31 +257,25 @@ impl<C: Configuration> IngredientImpl<C> {
}
#[cfg(feature = "persistence")]
pub unsafe fn as_serialize<'db, S>(
pub fn as_serialize<'db>(
&'db self,
zalsa: &'db Zalsa,
db: &'db dyn crate::Database,
) -> impl serde::Serialize + 'db {
persistence::SerializeIngredient {
zalsa,
_ingredient: self,
zalsa: db.zalsa(),
}
}
#[cfg(feature = "persistence")]
pub fn deserialize<'de, D>(
&mut self,
zalsa: &mut Zalsa,
deserializer: D,
) -> Result<(), D::Error>
where
D: serde::Deserializer<'de>,
{
let deserialize = persistence::DeserializeIngredient {
zalsa,
pub fn as_deserialize<'db>(
&'db mut self,
db: &'db mut dyn crate::Database,
) -> impl for<'de> serde::de::DeserializeSeed<'de, Value = ()> + 'db {
persistence::DeserializeIngredient {
ingredient: self,
};
serde::de::DeserializeSeed::deserialize(deserialize, deserializer)
zalsa: db.zalsa_mut(),
}
}
}

View file

@ -838,25 +838,25 @@ where
}
#[cfg(feature = "persistence")]
pub unsafe fn as_serialize<'db, S>(
pub fn as_serialize<'db>(
&'db self,
zalsa: &'db Zalsa,
db: &'db dyn crate::Database,
) -> impl serde::Serialize + 'db {
persistence::SerializeIngredient {
zalsa,
ingredient: self,
zalsa: db.zalsa(),
}
}
#[cfg(feature = "persistence")]
pub fn deserialize<'de, D>(&self, zalsa: &mut Zalsa, deserializer: D) -> Result<(), D::Error>
where
D: serde::Deserializer<'de>,
{
crate::database::persistence::with_mut_ingredient(self, zalsa, |ingredient, zalsa| {
let deserialize = persistence::DeserializeIngredient { zalsa, ingredient };
serde::de::DeserializeSeed::deserialize(deserialize, deserializer)
})
pub fn as_deserialize<'db>(
&'db mut self,
db: &'db mut dyn crate::Database,
) -> impl for<'de> serde::de::DeserializeSeed<'de, Value = ()> + 'db {
persistence::DeserializeIngredient {
ingredient: self,
zalsa: db.zalsa_mut(),
}
}
}

View file

@ -66,11 +66,11 @@ pub use self::revision::Revision;
pub use self::runtime::Runtime;
pub use self::storage::{Storage, StorageHandle};
pub use self::update::Update;
pub use self::zalsa::IngredientIndex;
pub use self::zalsa::{HasJar, IngredientIndex};
pub use crate::attach::{attach, with_attached_database};
#[cfg(feature = "persistence")]
pub use database::SerializeBuilder;
pub use self::database::persistence::with_mut_ingredient;
pub mod prelude {
#[cfg(feature = "accumulator")]
@ -104,7 +104,7 @@ pub mod plumbing {
pub use crate::database::{current_revision, Database};
pub use crate::durability::Durability;
pub use crate::id::{AsId, FromId, FromIdWithDb, Id};
pub use crate::ingredient::{Ingredient, Jar, Location, Persistable};
pub use crate::ingredient::{Ingredient, Jar, Location};
pub use crate::ingredient_cache::IngredientCache;
pub use crate::key::DatabaseKeyIndex;
pub use crate::memo_ingredient_indices::{

View file

@ -912,28 +912,25 @@ where
}
#[cfg(feature = "persistence")]
pub unsafe fn as_serialize<'db, S>(&'db self, zalsa: &'db Zalsa) -> impl serde::Serialize + 'db {
pub fn as_serialize<'db>(
&'db self,
db: &'db dyn crate::Database,
) -> impl serde::Serialize + 'db {
persistence::SerializeIngredient {
zalsa,
_ingredient: self,
zalsa: db.zalsa(),
}
}
#[cfg(feature = "persistence")]
pub fn deserialize<'de, D>(
&mut self,
zalsa: &mut Zalsa,
deserializer: D,
) -> Result<(), D::Error>
where
D: serde::Deserializer<'de>,
{
let deserialize = persistence::DeserializeIngredient {
zalsa,
pub fn as_deserialize<'db>(
&'db mut self,
db: &'db mut dyn crate::Database,
) -> impl for<'de> serde::de::DeserializeSeed<'de, Value = ()> + 'db {
persistence::DeserializeIngredient {
ingredient: self,
};
serde::de::DeserializeSeed::deserialize(deserialize, deserializer)
zalsa: db.zalsa_mut(),
}
}
}

View file

@ -3,10 +3,87 @@
mod common;
use common::LogDatabase;
use salsa::{Database, Durability, Setter};
use salsa::{Database, Durability, HasJar, Setter};
use expect_test::expect;
use serde::ser::SerializeTupleStruct;
struct SerializeDatabase<'db>(&'db dyn salsa::Database);
impl serde::Serialize for SerializeDatabase<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let SerializeDatabase(db) = *self;
let mut seq = serializer.serialize_tuple_struct("Database", 14)?;
seq.serialize_field(&db.as_serialize());
// TODO: `seq.serialize_field(salsa::serialize_ingredient::<MyInput>())`
seq.serialize_field(&MyInput::ingredient(db).as_serialize(db))?;
seq.serialize_field(&MySingleton::ingredient(db).as_serialize(db))?;
seq.serialize_field(&MyInterned::ingredient(db).as_serialize(db))?;
seq.serialize_field(&MyTracked::ingredient(db).as_serialize(db))?;
seq.serialize_field(&unit_to_interned::ingredient(db).as_serialize(db))?;
seq.serialize_field(&input_to_tracked::ingredient(db).as_serialize(db))?;
seq.serialize_field(&input_pair_to_string::ingredient(db).as_serialize(db))?;
seq.serialize_field(&partial_query::ingredient(db).as_serialize(db))?;
seq.serialize_field(&partial_query_inner::ingredient(db).as_serialize(db))?;
seq.serialize_field(&partial_query_intern::ingredient(db).as_serialize(db))?;
seq.serialize_field(&partial_query_intern_inner::ingredient(db).as_serialize(db))?;
seq.serialize_field(&specify::ingredient(db).as_serialize(db))?;
seq.serialize_field(&specified_query::ingredient(db).as_serialize(db))?;
seq.end()
}
}
struct DeserializeDatabase<'db>(&'db mut dyn salsa::Database);
impl<'de> serde::de::DeserializeSeed<'de> for DeserializeDatabase<'_> {
type Value = ();
fn deserialize<D>(self, deserializer: D) -> Result<(), D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_tuple_struct("Database", 14, self)
}
}
impl<'de> serde::de::Visitor<'de> for DeserializeDatabase<'_> {
type Value = ();
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "a sequence")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let DeserializeDatabase(db) = self;
seq.next_element_seed(db.as_deserialize());
// TODO: `salsa::deserialize_ingredient::<MyInput>(db, |seed| seq.next_element_seed(seed))`
salsa::with_mut_ingredient::<MyInput, _>(db, |i, db| {
seq.next_element_seed(i.as_deserialize(db))
})?;
Ok(())
}
}
#[salsa::input(persist)]
struct MyInput {
field: usize,
@ -49,8 +126,7 @@ fn everything() {
let _input1 = MyInput::new(&db, 1);
let _input2 = MyInput::new(&db, 2);
let serialized =
serde_json::to_string_pretty(&<dyn salsa::Database>::as_serialize(&mut db)).unwrap();
let serialized = serde_json::to_string_pretty(&SerializeDatabase(&db)).unwrap();
let expected = expect![[r#"
{
@ -99,8 +175,7 @@ fn everything() {
let _out = input_to_tracked(&db, input1);
let _out = input_pair_to_string(&db, input1, input2);
let serialized =
serde_json::to_string_pretty(&<dyn salsa::Database>::as_serialize(&mut db)).unwrap();
let serialized = serde_json::to_string_pretty(&SerializeDatabase(&db)).unwrap();
let expected = expect![[r#"
{
@ -312,30 +387,29 @@ fn everything() {
]"#]]);
}
#[test]
fn partial_query() {
use salsa::plumbing::{FromId, ZalsaDatabase};
#[salsa::tracked(persist)]
fn query<'db>(db: &'db dyn salsa::Database, input: MyInput) -> usize {
inner_query(db, input) + 1
}
#[salsa::tracked(persist)]
fn partial_query<'db>(db: &'db dyn salsa::Database, input: MyInput) -> usize {
// Note that the inner query is not persisted, but we should still preserve the dependency on `input.field`.
#[salsa::tracked]
fn inner_query<'db>(db: &'db dyn salsa::Database, input: MyInput) -> usize {
input.field(db)
}
partial_query_inner(db, input) + 1
}
#[salsa::tracked]
fn partial_query_inner<'db>(db: &'db dyn salsa::Database, input: MyInput) -> usize {
input.field(db)
}
#[test]
fn test_partial_query() {
use salsa::plumbing::{FromId, ZalsaDatabase};
let mut db = common::EventLoggerDatabase::default();
let input = MyInput::new(&db, 0);
let result = query(&db, input);
let result = partial_query(&db, input);
assert_eq!(result, 1);
let serialized =
serde_json::to_string_pretty(&<dyn salsa::Database>::as_serialize(&mut db)).unwrap();
let serialized = serde_json::to_string_pretty(&SerializeDatabase(&db)).unwrap();
let expected = expect![[r#"
{
"runtime": {
@ -397,7 +471,7 @@ fn partial_query() {
.expect("`MyInput` was persisted");
let input = MyInput::from_id(id.key_index());
let result = query(&db, input);
let result = partial_query(&db, input);
assert_eq!(result, 1);
// The query was not re-executed.
@ -409,7 +483,7 @@ fn partial_query() {
input.set_field(&mut db).to(1);
let result = query(&db, input);
let result = partial_query(&db, input);
assert_eq!(result, 2);
// The query was re-executed afer the input was updated.
@ -423,35 +497,38 @@ fn partial_query() {
]"#]]);
}
#[salsa::tracked(persist)]
fn partial_query_intern<'db>(
db: &'db dyn salsa::Database,
input: MyInput,
value: usize,
) -> MyInterned<'db> {
partial_query_intern_inner(db, input, value)
}
// Note that the inner query is not persisted, but we should still preserve the dependency on `MyInterned`.
#[salsa::tracked]
fn partial_query_intern_inner<'db>(
db: &'db dyn salsa::Database,
input: MyInput,
value: usize,
) -> MyInterned<'db> {
let _i = input.field(db); // Only low durability interned values are garbage collected.
MyInterned::new(db, value.to_string())
}
#[test]
fn partial_query_interned() {
use salsa::plumbing::{AsId, FromId, ZalsaDatabase};
#[salsa::tracked(persist)]
fn intern<'db>(db: &'db dyn salsa::Database, input: MyInput, value: usize) -> MyInterned<'db> {
do_intern(db, input, value)
}
// Note that the inner query is not persisted, but we should still preserve the dependency on `MyInterned`.
#[salsa::tracked]
fn do_intern<'db>(
db: &'db dyn salsa::Database,
input: MyInput,
value: usize,
) -> MyInterned<'db> {
let _i = input.field(db); // Only low durability interned values are garbage collected.
MyInterned::new(db, value.to_string())
}
let mut db = common::EventLoggerDatabase::default();
let input = MyInput::builder(0).durability(Durability::LOW).new(&db);
// Intern `i0`.
let i0 = intern(&db, input, 0);
let i0 = partial_query_intern(&db, input, 0);
assert_eq!(i0.field(&db), "0");
let serialized =
serde_json::to_string_pretty(&<dyn salsa::Database>::as_serialize(&mut db)).unwrap();
let serialized = serde_json::to_string_pretty(&SerializeDatabase(&db)).unwrap();
let expected = expect![[r#"
{
"runtime": {
@ -537,7 +614,7 @@ fn partial_query_interned() {
let input = MyInput::from_id(id.key_index());
// Re-intern `i0`.
let i0 = intern(&db, input, 0);
let i0 = partial_query_intern(&db, input, 0);
let i0_id = i0.as_id();
assert_eq!(i0.field(&db), "0");
@ -552,7 +629,7 @@ fn partial_query_interned() {
for x in 1.. {
db.synthetic_write(Durability::LOW);
let ix = intern(&db, input, x);
let ix = partial_query_intern(&db, input, x);
let ix_id = ix.as_id();
// We reused the slot of `i0`.
@ -562,7 +639,7 @@ fn partial_query_interned() {
}
// Re-intern `i0` after is has been garbage collected.
let i0 = intern(&db, input, 0);
let i0 = partial_query_intern(&db, input, 0);
// The query was re-executed due to garbage collection, even though no inputs have changed
// and the inner query was not persisted.
@ -570,47 +647,23 @@ fn partial_query_interned() {
assert_ne!(i0_id.index(), i0.as_id().index());
}
#[salsa::tracked]
fn specify<'db>(db: &'db dyn salsa::Database) {
let tracked = MyTracked::new(db, "a".to_string());
specified_query::specify(db, tracked, 2222);
}
#[salsa::tracked(specify, persist)]
fn specified_query<'db>(_db: &'db dyn salsa::Database, _tracked: MyTracked<'db>) -> u32 {
0
}
#[test]
#[should_panic(expected = "must be persistable")]
fn invalid_specified_dependency() {
#[salsa::tracked]
fn specify<'db>(db: &'db dyn salsa::Database) {
let tracked = MyTracked::new(db, "a".to_string());
specified_query::specify(db, tracked, 2222);
}
#[salsa::tracked(specify, persist)]
fn specified_query<'db>(_db: &'db dyn salsa::Database, _tracked: MyTracked<'db>) -> u32 {
0
}
let mut db = common::LoggerDatabase::default();
let db = common::LoggerDatabase::default();
specify(&db);
let _serialized =
serde_json::to_string_pretty(&<dyn salsa::Database>::as_serialize(&mut db)).unwrap();
}
#[test]
fn serialize_nothing() {
let mut db = common::LoggerDatabase::default();
let serialized =
serde_json::to_string_pretty(&<dyn salsa::Database>::as_serialize(&mut db)).unwrap();
// Empty ingredients should not be serialized.
let expected = expect![[r#"
{
"runtime": {
"revisions": [
1,
1,
1
]
},
"ingredients": {}
}"#]];
expected.assert_eq(&serialized);
let _serialized = serde_json::to_string_pretty(&SerializeDatabase(&db)).unwrap();
}