diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index a22963a5af..02fe9ccb21 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -696,22 +696,60 @@ impl Sql { self.query_queue .push_front(statement, vec![Argument::String(entity_id.to_string())]); for member in s.children.iter() { - if let Ty::Struct(_) = &member.ty { - let mut path_clone = path.clone(); - path_clone.push(member.name.clone()); - self.build_delete_entity_queries_recursive( - path_clone, entity_id, &member.ty, - ); - } + let mut path_clone = path.clone(); + path_clone.push(member.name.clone()); + self.build_delete_entity_queries_recursive(path_clone, entity_id, &member.ty); } } Ty::Enum(e) => { + if e.options + .iter() + .all(|o| if let Ty::Tuple(t) = &o.ty { t.is_empty() } else { false }) + { + return; + } + + let table_id = path.join("$"); + let statement = format!("DELETE FROM [{table_id}] WHERE entity_id = ?"); + self.query_queue + .push_front(statement, vec![Argument::String(entity_id.to_string())]); + for child in e.options.iter() { + if let Ty::Tuple(t) = &child.ty { + if t.is_empty() { + continue; + } + } + let mut path_clone = path.clone(); path_clone.push(child.name.clone()); self.build_delete_entity_queries_recursive(path_clone, entity_id, &child.ty); } } + Ty::Array(array) => { + let table_id = path.join("$"); + let statement = format!("DELETE FROM [{table_id}] WHERE entity_id = ?"); + self.query_queue + .push_front(statement, vec![Argument::String(entity_id.to_string())]); + + for member in array.iter() { + let mut path_clone = path.clone(); + path_clone.push("data".to_string()); + self.build_delete_entity_queries_recursive(path_clone, entity_id, member); + } + } + Ty::Tuple(t) => { + let table_id = path.join("$"); + let statement = format!("DELETE FROM [{table_id}] WHERE entity_id = ?"); + self.query_queue + .push_front(statement, vec![Argument::String(entity_id.to_string())]); + + for (idx, member) in t.iter().enumerate() { + let mut path_clone = path.clone(); + path_clone.push(format!("_{}", idx)); + self.build_delete_entity_queries_recursive(path_clone, entity_id, member); + } + } _ => {} } } diff --git a/crates/torii/core/src/sql_test.rs b/crates/torii/core/src/sql_test.rs index 09f4100caa..92d45a68e8 100644 --- a/crates/torii/core/src/sql_test.rs +++ b/crates/torii/core/src/sql_test.rs @@ -6,7 +6,7 @@ use dojo_test_utils::migration::prepare_migration; use dojo_world::contracts::world::WorldContractReader; use dojo_world::metadata::dojo_metadata_from_workspace; use dojo_world::migration::TxnConfig; -use dojo_world::utils::TransactionWaiter; +use dojo_world::utils::{TransactionExt, TransactionWaiter}; use katana_runner::KatanaRunner; use scarb::ops; use sozo_ops::migration::execute_strategy; @@ -16,11 +16,12 @@ use starknet::core::types::{BlockId, BlockTag}; use starknet::core::utils::get_selector_from_name; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{JsonRpcClient, Provider}; -use starknet_crypto::poseidon_hash_many; +use starknet_crypto::{poseidon_hash_many, FieldElement}; use tokio::sync::broadcast; use crate::engine::{Engine, EngineConfig, Processors}; use crate::processors::register_model::RegisterModelProcessor; +use crate::processors::store_del_record::StoreDelRecordProcessor; use crate::processors::store_set_record::StoreSetRecordProcessor; use crate::sql::Sql; @@ -38,7 +39,11 @@ where db, provider, Processors { - event: vec![Box::new(RegisterModelProcessor), Box::new(StoreSetRecordProcessor)], + event: vec![ + Box::new(RegisterModelProcessor), + Box::new(StoreSetRecordProcessor), + Box::new(StoreDelRecordProcessor), + ], ..Processors::default() }, EngineConfig::default(), @@ -156,9 +161,7 @@ async fn test_load_from_remote() { assert_eq!(packed_size, 0); assert_eq!(unpacked_size, 0); - // print all entities - let entities = sqlx::query("SELECT * FROM entities").fetch_all(&pool).await.unwrap(); - assert_eq!(entities.len(), 2); + assert_eq!(count_table("entities", &pool).await, 2); let (id, keys): (String, String) = sqlx::query_as( format!( @@ -176,3 +179,131 @@ async fn test_load_from_remote() { db.execute().await.unwrap(); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_load_from_remote_del() { + let options = + SqliteConnectOptions::from_str("sqlite::memory:").unwrap().create_if_missing(true); + let pool = SqlitePoolOptions::new().max_connections(5).connect_with(options).await.unwrap(); + sqlx::migrate!("../migrations").run(&pool).await.unwrap(); + + let source_project_dir = Utf8PathBuf::from("../../../examples/spawn-and-move/"); + let dojo_core_path = Utf8PathBuf::from("../../dojo-core"); + + let config = compiler::copy_tmp_config(&source_project_dir, &dojo_core_path); + let ws = scarb::ops::read_workspace(config.manifest_path(), &config).unwrap(); + let dojo_metadata = + dojo_metadata_from_workspace(&ws).expect("No current package with dojo metadata found."); + + let manifest_path = config.manifest_path(); + let base_dir = manifest_path.parent().unwrap(); + let target_dir = format!("{}/target/dev", base_dir); + + let mut migration = + prepare_migration(base_dir.into(), target_dir.into(), dojo_metadata.skip_migration) + .unwrap(); + migration.resolve_variable(migration.world_address().unwrap()).unwrap(); + + let sequencer = KatanaRunner::new().expect("Failed to start runner."); + + let provider = JsonRpcClient::new(HttpTransport::new(sequencer.url())); + + let world = WorldContractReader::new(migration.world_address().unwrap(), &provider); + + let mut account = sequencer.account(0); + account.set_block_id(BlockId::Tag(BlockTag::Pending)); + + let ws = ops::read_workspace(config.manifest_path(), &config) + .unwrap_or_else(|op| panic!("Error building workspace: {op:?}")); + + let migration_output = + execute_strategy(&ws, &migration, &account, TxnConfig::init_wait()).await.unwrap(); + + let world_address = migration_output.world_address; + + assert!(migration.world_address().unwrap() == world_address); + + // spawn + account + .execute(vec![Call { + to: migration_output + .contracts + .first() + .expect("shouldn't be empty") + .as_ref() + .expect("should be deployed") + .contract_address, + selector: get_selector_from_name("spawn").unwrap(), + calldata: vec![], + }]) + .send_with_cfg(&TxnConfig::init_wait()) + .await + .unwrap(); + + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + // Set player config. + account + .execute(vec![Call { + to: migration_output + .contracts + .first() + .expect("shouldn't be empty") + .as_ref() + .expect("should be deployed") + .contract_address, + selector: get_selector_from_name("set_player_config").unwrap(), + // Empty ByteArray. + calldata: vec![FieldElement::ZERO, FieldElement::ZERO, FieldElement::ZERO], + }]) + .send_with_cfg(&TxnConfig::init_wait()) + .await + .unwrap(); + + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + account + .execute(vec![Call { + to: migration_output + .contracts + .first() + .expect("shouldn't be empty") + .as_ref() + .expect("should be deployed") + .contract_address, + selector: get_selector_from_name("reset_player_config").unwrap(), + calldata: vec![], + }]) + .send_with_cfg(&TxnConfig::init_wait()) + .await + .unwrap(); + + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + let mut db = Sql::new(pool.clone(), world_address).await.unwrap(); + let _ = bootstrap_engine(world, db.clone(), &provider).await; + + assert_eq!(count_table("PlayerConfig", &pool).await, 0); + assert_eq!(count_table("PlayerConfig$favorite_item", &pool).await, 0); + assert_eq!(count_table("PlayerConfig$items", &pool).await, 0); + + // TODO: check how we can have a test that is more chronological with Torii re-syncing + // to ensure we can test intermediate states. + + db.execute().await.unwrap(); +} + +/// Count the number of rows in a table. +/// +/// # Arguments +/// * `table_name` - The name of the table to count the rows of. +/// * `pool` - The database pool. +/// +/// # Returns +/// The number of rows in the table. +async fn count_table(table_name: &str, pool: &sqlx::Pool) -> i64 { + let count_query = format!("SELECT COUNT(*) FROM {}", table_name); + let count: (i64,) = sqlx::query_as(&count_query).fetch_one(pool).await.unwrap(); + + count.0 +} diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 55bf0af53d..0ec4b351ac 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -10,7 +10,7 @@ dependencies = [ [[package]] name = "dojo_examples" -version = "0.7.0" +version = "0.7.1" dependencies = [ "dojo", ] diff --git a/examples/spawn-and-move/manifests/dev/abis/base/contracts/dojo_examples_actions_actions.json b/examples/spawn-and-move/manifests/dev/abis/base/contracts/dojo_examples_actions_actions.json index 0882f7c56d..4be28b68dd 100644 --- a/examples/spawn-and-move/manifests/dev/abis/base/contracts/dojo_examples_actions_actions.json +++ b/examples/spawn-and-move/manifests/dev/abis/base/contracts/dojo_examples_actions_actions.json @@ -218,6 +218,13 @@ } ], "state_mutability": "view" + }, + { + "type": "function", + "name": "reset_player_config", + "inputs": [], + "outputs": [], + "state_mutability": "external" } ] }, diff --git a/examples/spawn-and-move/manifests/dev/abis/deployments/contracts/dojo_examples_actions_actions.json b/examples/spawn-and-move/manifests/dev/abis/deployments/contracts/dojo_examples_actions_actions.json index 0882f7c56d..4be28b68dd 100644 --- a/examples/spawn-and-move/manifests/dev/abis/deployments/contracts/dojo_examples_actions_actions.json +++ b/examples/spawn-and-move/manifests/dev/abis/deployments/contracts/dojo_examples_actions_actions.json @@ -218,6 +218,13 @@ } ], "state_mutability": "view" + }, + { + "type": "function", + "name": "reset_player_config", + "inputs": [], + "outputs": [], + "state_mutability": "external" } ] }, diff --git a/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples_actions_actions.toml b/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples_actions_actions.toml index f1134d5536..b1680ae07c 100644 --- a/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples_actions_actions.toml +++ b/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples_actions_actions.toml @@ -1,6 +1,6 @@ kind = "DojoContract" -class_hash = "0x54ef672addb35455d35217a62c4293f2dc681b20737716b4ef9630d759d7a96" -original_class_hash = "0x54ef672addb35455d35217a62c4293f2dc681b20737716b4ef9630d759d7a96" +class_hash = "0x3b42f80dc8ac4628b0ed6c89af9055314c0aa2192ea0d9601f262138a1e50c3" +original_class_hash = "0x3b42f80dc8ac4628b0ed6c89af9055314c0aa2192ea0d9601f262138a1e50c3" base_class_hash = "0x0" abi = "manifests/dev/abis/base/contracts/dojo_examples_actions_actions.json" reads = [] diff --git a/examples/spawn-and-move/manifests/dev/manifest.json b/examples/spawn-and-move/manifests/dev/manifest.json index 133a1a9e54..a1a575d4aa 100644 --- a/examples/spawn-and-move/manifests/dev/manifest.json +++ b/examples/spawn-and-move/manifests/dev/manifest.json @@ -1020,8 +1020,8 @@ { "kind": "DojoContract", "address": "0x5c70a663d6b48d8e4c6aaa9572e3735a732ac3765700d470463e670587852af", - "class_hash": "0x386b6b90ce188fb732305f8742885ae2fb12f120d2616d6b4d396a0fcdf3590", - "original_class_hash": "0x386b6b90ce188fb732305f8742885ae2fb12f120d2616d6b4d396a0fcdf3590", + "class_hash": "0x3b42f80dc8ac4628b0ed6c89af9055314c0aa2192ea0d9601f262138a1e50c3", + "original_class_hash": "0x3b42f80dc8ac4628b0ed6c89af9055314c0aa2192ea0d9601f262138a1e50c3", "base_class_hash": "0x22f3e55b61d86c2ac5239fa3b3b8761f26b9a5c0b5f61ddbd5d756ced498b46", "abi": [ { @@ -1243,6 +1243,13 @@ } ], "state_mutability": "view" + }, + { + "type": "function", + "name": "reset_player_config", + "inputs": [], + "outputs": [], + "state_mutability": "external" } ] }, diff --git a/examples/spawn-and-move/manifests/dev/manifest.toml b/examples/spawn-and-move/manifests/dev/manifest.toml index 71caee992c..c292ce25af 100644 --- a/examples/spawn-and-move/manifests/dev/manifest.toml +++ b/examples/spawn-and-move/manifests/dev/manifest.toml @@ -22,8 +22,8 @@ name = "dojo::base::base" [[contracts]] kind = "DojoContract" address = "0x5c70a663d6b48d8e4c6aaa9572e3735a732ac3765700d470463e670587852af" -class_hash = "0x386b6b90ce188fb732305f8742885ae2fb12f120d2616d6b4d396a0fcdf3590" -original_class_hash = "0x386b6b90ce188fb732305f8742885ae2fb12f120d2616d6b4d396a0fcdf3590" +class_hash = "0x3b42f80dc8ac4628b0ed6c89af9055314c0aa2192ea0d9601f262138a1e50c3" +original_class_hash = "0x3b42f80dc8ac4628b0ed6c89af9055314c0aa2192ea0d9601f262138a1e50c3" base_class_hash = "0x22f3e55b61d86c2ac5239fa3b3b8761f26b9a5c0b5f61ddbd5d756ced498b46" abi = "manifests/dev/abis/deployments/contracts/dojo_examples_actions_actions.json" reads = [] diff --git a/examples/spawn-and-move/src/actions.cairo b/examples/spawn-and-move/src/actions.cairo index 1e9140b308..6737cdea48 100644 --- a/examples/spawn-and-move/src/actions.cairo +++ b/examples/spawn-and-move/src/actions.cairo @@ -6,6 +6,7 @@ trait IActions { fn move(ref world: IWorldDispatcher, direction: Direction); fn set_player_config(ref world: IWorldDispatcher, name: ByteArray); fn get_player_position(world: @IWorldDispatcher) -> Position; + fn reset_player_config(ref world: IWorldDispatcher); } #[dojo::interface] @@ -89,6 +90,27 @@ mod actions { set!(world, (config)); } + fn reset_player_config(ref world: IWorldDispatcher) { + let player = get_caller_address(); + + let (position, moves, config) = get!(world, player, (Position, Moves, PlayerConfig)); + + delete!(world, (position, moves, config)); + + let (position, moves, config) = get!(world, player, (Position, Moves, PlayerConfig)); + + assert(moves.remaining == 0, 'bad remaining'); + assert(moves.last_direction == Direction::None, 'bad last direction'); + + assert(position.vec.x == 0, 'bad x'); + assert(position.vec.y == 0, 'bad y'); + + assert(config.items.len() == 0, 'bad items'); + assert(config.favorite_item == Option::Some(0), 'bad favorite item'); + let empty_string: ByteArray = ""; + assert(config.name == empty_string, 'bad name'); + } + fn get_player_position(world: @IWorldDispatcher) -> Position { let player = get_caller_address(); get!(world, player, (Position)) diff --git a/examples/spawn-and-move/src/models.cairo b/examples/spawn-and-move/src/models.cairo index 85cfdf1711..443c3a2e53 100644 --- a/examples/spawn-and-move/src/models.cairo +++ b/examples/spawn-and-move/src/models.cairo @@ -1,6 +1,6 @@ use starknet::ContractAddress; -#[derive(Serde, Copy, Drop, Introspect)] +#[derive(Serde, Copy, Drop, Introspect, PartialEq)] enum Direction { None, Left,