Skip to content

Commit

Permalink
refactor(torii): delete entity in all tables & array references for e…
Browse files Browse the repository at this point in the history
…ntity delete! (#2072)
  • Loading branch information
Larkooo authored Jun 19, 2024
1 parent aa92675 commit fc44c36
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 21 deletions.
52 changes: 45 additions & 7 deletions crates/torii/core/src/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
_ => {}
}
}
Expand Down
143 changes: 137 additions & 6 deletions crates/torii/core/src/sql_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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(),
Expand Down Expand Up @@ -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!(
Expand All @@ -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<sqlx::Sqlite>) -> 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
}
2 changes: 1 addition & 1 deletion examples/spawn-and-move/Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ dependencies = [

[[package]]
name = "dojo_examples"
version = "0.7.0"
version = "0.7.1"
dependencies = [
"dojo",
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,13 @@
}
],
"state_mutability": "view"
},
{
"type": "function",
"name": "reset_player_config",
"inputs": [],
"outputs": [],
"state_mutability": "external"
}
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,13 @@
}
],
"state_mutability": "view"
},
{
"type": "function",
"name": "reset_player_config",
"inputs": [],
"outputs": [],
"state_mutability": "external"
}
]
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = []
Expand Down
11 changes: 9 additions & 2 deletions examples/spawn-and-move/manifests/dev/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand Down Expand Up @@ -1243,6 +1243,13 @@
}
],
"state_mutability": "view"
},
{
"type": "function",
"name": "reset_player_config",
"inputs": [],
"outputs": [],
"state_mutability": "external"
}
]
},
Expand Down
4 changes: 2 additions & 2 deletions examples/spawn-and-move/manifests/dev/manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
22 changes: 22 additions & 0 deletions examples/spawn-and-move/src/actions.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion examples/spawn-and-move/src/models.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use starknet::ContractAddress;

#[derive(Serde, Copy, Drop, Introspect)]
#[derive(Serde, Copy, Drop, Introspect, PartialEq)]
enum Direction {
None,
Left,
Expand Down

0 comments on commit fc44c36

Please sign in to comment.