From bb131a1824929c73d64733294e0907a74cfd502d Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 25 Nov 2024 13:14:01 +0700 Subject: [PATCH 01/41] start sql model register refactor --- crates/torii/core/src/sql/mod.rs | 421 ++++++++++--------------------- 1 file changed, 138 insertions(+), 283 deletions(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 5f7a8f5093..121843d24f 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -619,88 +619,6 @@ impl Sql { Ok(()) } - #[allow(clippy::too_many_arguments)] - fn build_register_queries_recursive( - &mut self, - selector: Felt, - model: &Ty, - path: Vec, - model_idx: &mut i64, - block_timestamp: u64, - array_idx: &mut usize, - parent_array_idx: &mut usize, - upgrade_diff: Option<&Ty>, - ) -> Result<()> { - if let Ty::Enum(e) = model { - if e.options.iter().all(|o| if let Ty::Tuple(t) = &o.ty { t.is_empty() } else { false }) - { - return Ok(()); - } - } - - self.build_model_query( - selector, - path.clone(), - model, - *model_idx, - block_timestamp, - *array_idx, - *parent_array_idx, - upgrade_diff, - )?; - - let mut build_member = |pathname: &str, member: &Ty| -> Result<()> { - if let Ty::Primitive(_) = member { - return Ok(()); - } else if let Ty::ByteArray(_) = member { - return Ok(()); - } - - let mut path_clone = path.clone(); - path_clone.push(pathname.to_string()); - - self.build_register_queries_recursive( - selector, - member, - path_clone, - &mut (*model_idx + 1), - block_timestamp, - &mut (*array_idx + if let Ty::Array(_) = member { 1 } else { 0 }), - &mut (*parent_array_idx + if let Ty::Array(_) = model { 1 } else { 0 }), - // nested members are not upgrades - None, - )?; - - Ok(()) - }; - - if let Ty::Struct(s) = model { - for member in s.children.iter() { - build_member(&member.name, &member.ty)?; - } - } else if let Ty::Tuple(t) = model { - for (idx, member) in t.iter().enumerate() { - build_member(format!("_{}", idx).as_str(), member)?; - } - } else if let Ty::Array(array) = model { - let ty = &array[0]; - build_member("data", ty)?; - } else if let Ty::Enum(e) = model { - for child in e.options.iter() { - // Skip enum options that have no type / member - if let Ty::Tuple(t) = &child.ty { - if t.is_empty() { - continue; - } - } - - build_member(&child.name, &child.ty)?; - } - } - - Ok(()) - } - fn build_set_entity_queries_recursive( &mut self, path: Vec, @@ -1027,94 +945,99 @@ impl Sql { parent_array_idx: usize, upgrade_diff: Option<&Ty>, ) -> Result<()> { - let table_id = path.join("$"); + let table_id = path[0].clone(); // Use only the root path component + let mut columns = Vec::new(); let mut indices = Vec::new(); + let mut alter_table_queries = Vec::new(); + // Start building the create table query let mut create_table_query = format!( "CREATE TABLE IF NOT EXISTS [{table_id}] (id TEXT NOT NULL, event_id TEXT NOT NULL, \ entity_id TEXT, event_message_id TEXT, " ); - let mut alter_table_queries = Vec::new(); - + // Handle array indexing columns if needed if array_idx > 0 { - // index columns for i in 0..array_idx { let column = format!("idx_{i} INTEGER NOT NULL"); create_table_query.push_str(&format!("{column}, ")); - alter_table_queries.push(format!( "ALTER TABLE [{table_id}] ADD COLUMN idx_{i} INTEGER NOT NULL DEFAULT 0" )); } - // full array id column create_table_query.push_str("full_array_id TEXT NOT NULL UNIQUE, "); alter_table_queries.push(format!( "ALTER TABLE [{table_id}] ADD COLUMN full_array_id TEXT NOT NULL UNIQUE DEFAULT ''" )); } - let mut build_member = |name: &str, ty: &Ty, options: &mut Option| { - if let Ok(cairo_type) = Primitive::from_str(&ty.name()) { - let sql_type = cairo_type.to_sql_type(); - let column = format!("external_{name} {sql_type}"); - - create_table_query.push_str(&format!("{column}, ")); - - alter_table_queries.push(format!( - "ALTER TABLE [{table_id}] ADD COLUMN external_{name} {sql_type}" - )); - - indices.push(format!( - "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{name}] ON [{table_id}] \ - (external_{name});" - )); - } else if let Ty::Enum(e) = &ty { - let all_options = e - .options - .iter() - .map(|c| format!("'{}'", c.name)) - .collect::>() - .join(", "); + // Recursively add columns for all nested types + self.add_columns_recursive( + &path, + model, + &mut columns, + &mut indices, + &table_id, + upgrade_diff, + )?; - let column = - format!("external_{name} TEXT CHECK(external_{name} IN ({all_options}))",); + // Add all columns to the create table query + for column in columns { + create_table_query.push_str(&format!("{}, ", column)); + } - create_table_query.push_str(&format!("{column}, ")); + // Add standard timestamps + create_table_query.push_str("executed_at DATETIME NOT NULL, "); + create_table_query.push_str("created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); + create_table_query.push_str("updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); - alter_table_queries.push(format!("ALTER TABLE [{table_id}] ADD COLUMN {column}")); + // Add primary key + create_table_query.push_str("PRIMARY KEY (id"); + for i in 0..array_idx { + create_table_query.push_str(&format!(", idx_{i}")); + } + create_table_query.push_str("), "); - indices.push(format!( - "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{name}] ON [{table_id}] \ - (external_{name});" - )); + // Add foreign key constraints + create_table_query.push_str("FOREIGN KEY (entity_id) REFERENCES entities(id), "); + create_table_query.push_str("FOREIGN KEY (event_message_id) REFERENCES event_messages(id));"); - *options = Some(Argument::String( - e.options - .iter() - .map(|c: &dojo_types::schema::EnumOption| c.name.clone()) - .collect::>() - .join(",") - .to_string(), - )); - } else if let Ty::ByteArray(_) = &ty { - let column = format!("external_{name} TEXT"); + // Execute the queries + if upgrade_diff.is_some() { + for alter_query in alter_table_queries { + self.executor.send(QueryMessage::other(alter_query, vec![]))?; + } + } else { + self.executor.send(QueryMessage::other(create_table_query, vec![]))?; + } - create_table_query.push_str(&format!("{column}, ")); + // Create indices + for index_query in indices { + self.executor.send(QueryMessage::other(index_query, vec![]))?; + } - alter_table_queries.push(format!("ALTER TABLE [{table_id}] ADD COLUMN {column}")); + Ok(()) + } - indices.push(format!( - "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{name}] ON [{table_id}] \ - (external_{name});" - )); - } + fn add_columns_recursive( + &mut self, + path: &[String], + ty: &Ty, + columns: &mut Vec, + indices: &mut Vec, + table_id: &str, + upgrade_diff: Option<&Ty>, + ) -> Result<()> { + let column_prefix = if path.len() > 1 { + path[1..].join("_") + } else { + String::new() }; - match model { + match ty { Ty::Struct(s) => { - for (member_idx, member) in s.children.iter().enumerate() { + for member in s.children.iter() { if let Some(upgrade_diff) = upgrade_diff { if !upgrade_diff .as_struct() @@ -1127,172 +1050,104 @@ impl Sql { } } - let name = member.name.clone(); - let mut options = None; // TEMP: doesnt support complex enums yet - - build_member(&name, &member.ty, &mut options); - - // NOTE: this might cause some errors to fail silently - // due to the ignore clause. check migrations for type_enum check - let statement = "INSERT OR IGNORE INTO model_members (id, model_id, \ - model_idx, member_idx, name, type, type_enum, enum_options, \ - key, executed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - - let arguments = vec![ - Argument::String(table_id.clone()), - // TEMP: this is temporary until the model hash is precomputed - Argument::String(format!("{:#x}", selector)), - Argument::Int(model_idx), - Argument::Int(member_idx as i64), - Argument::String(name), - Argument::String(member.ty.name()), - Argument::String(member.ty.as_ref().into()), - options.unwrap_or(Argument::Null), - Argument::Bool(member.key), - Argument::String(utc_dt_string_from_timestamp(block_timestamp)), - ]; - - self.executor.send(QueryMessage::other(statement.to_string(), arguments))?; + let mut new_path = path.to_vec(); + new_path.push(member.name.clone()); + + self.add_columns_recursive( + &new_path, + &member.ty, + columns, + indices, + table_id, + None, + )?; } } Ty::Tuple(tuple) => { for (idx, member) in tuple.iter().enumerate() { - let mut options = None; // TEMP: doesnt support complex enums yet - - build_member(&format!("_{}", idx), member, &mut options); - - let statement = "INSERT OR IGNORE INTO model_members (id, model_id, \ - model_idx, member_idx, name, type, type_enum, enum_options, \ - key, executed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - let arguments = vec![ - Argument::String(table_id.clone()), - // TEMP: this is temporary until the model hash is precomputed - Argument::String(format!("{:#x}", selector)), - Argument::Int(model_idx), - Argument::Int(idx as i64), - Argument::String(format!("_{}", idx)), - Argument::String(member.name()), - Argument::String(member.as_ref().into()), - options.unwrap_or(Argument::Null), - // NOTE: should we consider the case where - // a tuple is used as a key? should its members be keys? - Argument::Bool(false), - Argument::String(utc_dt_string_from_timestamp(block_timestamp)), - ]; - - self.executor.send(QueryMessage::other(statement.to_string(), arguments))?; + let mut new_path = path.to_vec(); + new_path.push(format!("_{}", idx)); + + self.add_columns_recursive( + &new_path, + member, + columns, + indices, + table_id, + None, + )?; } } Ty::Array(array) => { - let mut options = None; // TEMP: doesnt support complex enums yet - let ty = &array[0]; - build_member("data", ty, &mut options); - - let statement = "INSERT OR IGNORE INTO model_members (id, model_id, model_idx, \ - member_idx, name, type, type_enum, enum_options, key, \ - executed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - let arguments = vec![ - Argument::String(table_id.clone()), - // TEMP: this is temporary until the model hash is precomputed - Argument::String(format!("{:#x}", selector)), - Argument::Int(model_idx), - Argument::Int(0), - Argument::String("data".to_string()), - Argument::String(ty.name()), - Argument::String(ty.as_ref().into()), - options.unwrap_or(Argument::Null), - Argument::Bool(false), - Argument::String(utc_dt_string_from_timestamp(block_timestamp)), - ]; - - self.executor.send(QueryMessage::other(statement.to_string(), arguments))?; + let mut new_path = path.to_vec(); + new_path.push("data".to_string()); + + self.add_columns_recursive( + &new_path, + &array[0], + columns, + indices, + table_id, + None, + )?; } Ty::Enum(e) => { - for (idx, child) in e + // Add the enum option column + let column_name = if column_prefix.is_empty() { + format!("external_option") + } else { + format!("external_{}_option", column_prefix) + }; + + let all_options = e .options .iter() - .chain(vec![&EnumOption { - name: "option".to_string(), - ty: Ty::Enum(e.clone()), - }]) - .enumerate() - { - // Skip enum options that have no type / member + .map(|c| format!("'{}'", c.name)) + .collect::>() + .join(", "); + + columns.push(format!("{} TEXT CHECK({} IN ({}))", column_name, column_name, all_options)); + indices.push(format!( + "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{column_name}] ON [{table_id}] ({column_name});" + )); + + // Add columns for each enum variant's data + for child in e.options.iter() { if let Ty::Tuple(tuple) = &child.ty { if tuple.is_empty() { continue; } } - let mut options = None; // TEMP: doesnt support complex enums yet - build_member(&child.name, &child.ty, &mut options); - - let statement = "INSERT OR IGNORE INTO model_members (id, model_id, \ - model_idx, member_idx, name, type, type_enum, enum_options, \ - key, executed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - let arguments = vec![ - Argument::String(table_id.clone()), - // TEMP: this is temporary until the model hash is precomputed - Argument::String(format!("{:#x}", selector)), - Argument::Int(model_idx), - Argument::Int(idx as i64), - Argument::String(child.name.clone()), - Argument::String(child.ty.name()), - Argument::String(child.ty.as_ref().into()), - options.unwrap_or(Argument::Null), - Argument::Bool(false), - Argument::String(utc_dt_string_from_timestamp(block_timestamp)), - ]; - - self.executor.send(QueryMessage::other(statement.to_string(), arguments))?; + let mut new_path = path.to_vec(); + new_path.push(child.name.clone()); + + self.add_columns_recursive( + &new_path, + &child.ty, + columns, + indices, + table_id, + None, + )?; } } - _ => {} - } - - create_table_query.push_str("executed_at DATETIME NOT NULL, "); - create_table_query.push_str("created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); - create_table_query.push_str("updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); - - // If this is not the Model's root table, create a reference to the parent. - if path.len() > 1 { - let parent_table_id = path[..path.len() - 1].join("$"); - - create_table_query.push_str("FOREIGN KEY (id"); - for i in 0..parent_array_idx { - create_table_query.push_str(&format!(", idx_{i}", i = i)); - } - create_table_query.push_str(&format!( - ") REFERENCES [{parent_table_id}] (id", - parent_table_id = parent_table_id - )); - for i in 0..parent_array_idx { - create_table_query.push_str(&format!(", idx_{i}", i = i)); - } - create_table_query.push_str(") ON DELETE CASCADE, "); - }; - - create_table_query.push_str("PRIMARY KEY (id"); - for i in 0..array_idx { - create_table_query.push_str(&format!(", idx_{i}", i = i)); - } - create_table_query.push_str("), "); - - create_table_query.push_str("FOREIGN KEY (entity_id) REFERENCES entities(id), "); - // create_table_query.push_str("FOREIGN KEY (event_id) REFERENCES events(id), "); - create_table_query - .push_str("FOREIGN KEY (event_message_id) REFERENCES event_messages(id));"); - - if upgrade_diff.is_some() { - for alter_query in alter_table_queries { - self.executor.send(QueryMessage::other(alter_query, vec![]))?; + _ => { + // Handle primitive types + if let Ok(cairo_type) = Primitive::from_str(&ty.name()) { + let sql_type = cairo_type.to_sql_type(); + let column_name = if column_prefix.is_empty() { + format!("external_value") + } else { + format!("external_{}", column_prefix) + }; + + columns.push(format!("{} {}", column_name, sql_type)); + indices.push(format!( + "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{column_name}] ON [{table_id}] ({column_name});" + )); + } } - } else { - self.executor.send(QueryMessage::other(create_table_query, vec![]))?; - } - - for index_query in indices { - self.executor.send(QueryMessage::other(index_query, vec![]))?; } Ok(()) From a82a895ff98b36706294833948f8185c8cf836a4 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 25 Nov 2024 13:27:02 +0700 Subject: [PATCH 02/41] refactor: arrays as json & model members --- crates/torii/core/src/sql/mod.rs | 86 ++++++++++++-------------------- 1 file changed, 33 insertions(+), 53 deletions(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 121843d24f..0aefcc2978 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -941,8 +941,8 @@ impl Sql { model: &Ty, model_idx: i64, block_timestamp: u64, - array_idx: usize, - parent_array_idx: usize, + _array_idx: usize, + _parent_array_idx: usize, upgrade_diff: Option<&Ty>, ) -> Result<()> { let table_id = path[0].clone(); // Use only the root path component @@ -950,28 +950,15 @@ impl Sql { let mut indices = Vec::new(); let mut alter_table_queries = Vec::new(); - // Start building the create table query + // Start building the create table query with internal columns let mut create_table_query = format!( - "CREATE TABLE IF NOT EXISTS [{table_id}] (id TEXT NOT NULL, event_id TEXT NOT NULL, \ - entity_id TEXT, event_message_id TEXT, " + "CREATE TABLE IF NOT EXISTS [{table_id}] (\ + internal_id TEXT NOT NULL PRIMARY KEY, \ + internal_event_id TEXT NOT NULL, \ + internal_entity_id TEXT, \ + internal_event_message_id TEXT, " ); - // Handle array indexing columns if needed - if array_idx > 0 { - for i in 0..array_idx { - let column = format!("idx_{i} INTEGER NOT NULL"); - create_table_query.push_str(&format!("{column}, ")); - alter_table_queries.push(format!( - "ALTER TABLE [{table_id}] ADD COLUMN idx_{i} INTEGER NOT NULL DEFAULT 0" - )); - } - - create_table_query.push_str("full_array_id TEXT NOT NULL UNIQUE, "); - alter_table_queries.push(format!( - "ALTER TABLE [{table_id}] ADD COLUMN full_array_id TEXT NOT NULL UNIQUE DEFAULT ''" - )); - } - // Recursively add columns for all nested types self.add_columns_recursive( &path, @@ -987,21 +974,14 @@ impl Sql { create_table_query.push_str(&format!("{}, ", column)); } - // Add standard timestamps - create_table_query.push_str("executed_at DATETIME NOT NULL, "); - create_table_query.push_str("created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); - create_table_query.push_str("updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); - - // Add primary key - create_table_query.push_str("PRIMARY KEY (id"); - for i in 0..array_idx { - create_table_query.push_str(&format!(", idx_{i}")); - } - create_table_query.push_str("), "); + // Add internal timestamps + create_table_query.push_str("internal_executed_at DATETIME NOT NULL, "); + create_table_query.push_str("internal_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); + create_table_query.push_str("internal_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); // Add foreign key constraints - create_table_query.push_str("FOREIGN KEY (entity_id) REFERENCES entities(id), "); - create_table_query.push_str("FOREIGN KEY (event_message_id) REFERENCES event_messages(id));"); + create_table_query.push_str("FOREIGN KEY (internal_entity_id) REFERENCES entities(id), "); + create_table_query.push_str("FOREIGN KEY (internal_event_message_id) REFERENCES event_messages(id));"); // Execute the queries if upgrade_diff.is_some() { @@ -1059,14 +1039,14 @@ impl Sql { columns, indices, table_id, - None, + upgrade_diff, )?; } } Ty::Tuple(tuple) => { for (idx, member) in tuple.iter().enumerate() { let mut new_path = path.to_vec(); - new_path.push(format!("_{}", idx)); + new_path.push(idx.to_string()); self.add_columns_recursive( &new_path, @@ -1074,29 +1054,29 @@ impl Sql { columns, indices, table_id, - None, + upgrade_diff, )?; } } - Ty::Array(array) => { - let mut new_path = path.to_vec(); - new_path.push("data".to_string()); + Ty::Array(_) => { + // Store array as JSON in a single column + let column_name = if column_prefix.is_empty() { + "value".to_string() + } else { + column_prefix + }; - self.add_columns_recursive( - &new_path, - &array[0], - columns, - indices, - table_id, - None, - )?; + columns.push(format!("{} TEXT", column_name)); // JSON will be stored as TEXT + indices.push(format!( + "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{column_name}] ON [{table_id}] ({column_name});" + )); } Ty::Enum(e) => { // Add the enum option column let column_name = if column_prefix.is_empty() { - format!("external_option") + "option".to_string() } else { - format!("external_{}_option", column_prefix) + format!("{}_option", column_prefix) }; let all_options = e @@ -1128,7 +1108,7 @@ impl Sql { columns, indices, table_id, - None, + upgrade_diff, )?; } } @@ -1137,9 +1117,9 @@ impl Sql { if let Ok(cairo_type) = Primitive::from_str(&ty.name()) { let sql_type = cairo_type.to_sql_type(); let column_name = if column_prefix.is_empty() { - format!("external_value") + "value".to_string() } else { - format!("external_{}", column_prefix) + column_prefix }; columns.push(format!("{} {}", column_name, sql_type)); From 57ce39a555134f9996b4db34e953269a54689cd0 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 25 Nov 2024 14:59:03 +0700 Subject: [PATCH 03/41] alter table and model idx --- crates/torii/core/src/sql/mod.rs | 200 ++++++++++++++++++++++++++----- 1 file changed, 168 insertions(+), 32 deletions(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 0aefcc2978..2bde4f905a 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -285,14 +285,12 @@ impl Sql { ))?; let mut model_idx = 0_i64; - self.build_register_queries_recursive( + self.build_model_query( selector, - model, vec![namespaced_name.clone()], - &mut model_idx, + model, + model_idx, block_timestamp, - &mut 0, - &mut 0, upgrade_diff, )?; @@ -939,10 +937,8 @@ impl Sql { selector: Felt, path: Vec, model: &Ty, - model_idx: i64, + mut model_idx: i64, block_timestamp: u64, - _array_idx: usize, - _parent_array_idx: usize, upgrade_diff: Option<&Ty>, ) -> Result<()> { let table_id = path[0].clone(); // Use only the root path component @@ -959,13 +955,17 @@ impl Sql { internal_event_message_id TEXT, " ); - // Recursively add columns for all nested types + // Recursively add columns for all nested type self.add_columns_recursive( &path, model, &mut columns, + &mut alter_table_queries, &mut indices, &table_id, + selector, + &mut model_idx, + block_timestamp, upgrade_diff, )?; @@ -1005,8 +1005,12 @@ impl Sql { path: &[String], ty: &Ty, columns: &mut Vec, + alter_table_queries: &mut Vec, indices: &mut Vec, table_id: &str, + selector: Felt, + model_idx: &mut i64, + block_timestamp: u64, upgrade_diff: Option<&Ty>, ) -> Result<()> { let column_prefix = if path.len() > 1 { @@ -1015,9 +1019,22 @@ impl Sql { String::new() }; + let mut add_column = |name: &str, sql_type: &str| { + if upgrade_diff.is_some() { + alter_table_queries.push(format!( + "ALTER TABLE [{table_id}] ADD COLUMN {name} {sql_type}" + )); + } else { + columns.push(format!("{name} {sql_type}")); + } + indices.push(format!( + "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{name}] ON [{table_id}] ({name});" + )); + }; + match ty { Ty::Struct(s) => { - for member in s.children.iter() { + for (member_idx, member) in s.children.iter().enumerate() { if let Some(upgrade_diff) = upgrade_diff { if !upgrade_diff .as_struct() @@ -1032,47 +1049,89 @@ impl Sql { let mut new_path = path.to_vec(); new_path.push(member.name.clone()); + + self.add_model_member( + table_id, + selector, + *model_idx, + member_idx as i64, + &member.name, + &member.ty, + member.key, + block_timestamp, + )?; + + *model_idx += 1; // Increment before recursing self.add_columns_recursive( &new_path, &member.ty, columns, + alter_table_queries, indices, table_id, - upgrade_diff, + selector, + model_idx, + block_timestamp, + None, )?; } } Ty::Tuple(tuple) => { for (idx, member) in tuple.iter().enumerate() { let mut new_path = path.to_vec(); - new_path.push(idx.to_string()); + new_path.push(format!("_{}", idx)); + + self.add_model_member( + table_id, + selector, + *model_idx, + idx as i64, + &format!("_{}", idx), + member, + false, + block_timestamp, + )?; + + *model_idx += 1; // Increment before recursing self.add_columns_recursive( &new_path, member, columns, + alter_table_queries, indices, table_id, - upgrade_diff, + selector, + model_idx, + block_timestamp, + None, )?; } } Ty::Array(_) => { - // Store array as JSON in a single column let column_name = if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; - columns.push(format!("{} TEXT", column_name)); // JSON will be stored as TEXT - indices.push(format!( - "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{column_name}] ON [{table_id}] ({column_name});" - )); + add_column(&column_name, "TEXT"); + + self.add_model_member( + table_id, + selector, + *model_idx, + 0, + &column_name, + ty, + false, + block_timestamp, + )?; + + *model_idx += 1; // Increment after adding array } Ty::Enum(e) => { - // Add the enum option column let column_name = if column_prefix.is_empty() { "option".to_string() } else { @@ -1086,13 +1145,23 @@ impl Sql { .collect::>() .join(", "); - columns.push(format!("{} TEXT CHECK({} IN ({}))", column_name, column_name, all_options)); - indices.push(format!( - "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{column_name}] ON [{table_id}] ({column_name});" - )); + let sql_type = format!("TEXT CHECK({} IN ({}))", column_name, all_options); + add_column(&column_name, &sql_type); - // Add columns for each enum variant's data - for child in e.options.iter() { + self.add_model_member( + table_id, + selector, + *model_idx, + 0, + &column_name, + ty, + false, + block_timestamp, + )?; + + *model_idx += 1; // Increment after adding enum option + + for (idx, child) in e.options.iter().enumerate() { if let Ty::Tuple(tuple) = &child.ty { if tuple.is_empty() { continue; @@ -1101,31 +1170,54 @@ impl Sql { let mut new_path = path.to_vec(); new_path.push(child.name.clone()); + + self.add_model_member( + table_id, + selector, + *model_idx, + idx as i64 + 1, + &child.name, + &child.ty, + false, + block_timestamp, + )?; + + *model_idx += 1; // Increment before recursing self.add_columns_recursive( &new_path, &child.ty, columns, + alter_table_queries, indices, table_id, - upgrade_diff, + selector, + model_idx, + block_timestamp, + None, )?; } } _ => { - // Handle primitive types if let Ok(cairo_type) = Primitive::from_str(&ty.name()) { - let sql_type = cairo_type.to_sql_type(); let column_name = if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; - columns.push(format!("{} {}", column_name, sql_type)); - indices.push(format!( - "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{column_name}] ON [{table_id}] ({column_name});" - )); + add_column(&column_name, &cairo_type.to_sql_type().to_string()); + + self.add_model_member( + table_id, + selector, + *model_idx, + 0, + &column_name, + ty, + false, + block_timestamp, + )?; } } } @@ -1133,6 +1225,50 @@ impl Sql { Ok(()) } + fn add_model_member( + &mut self, + table_id: &str, + selector: Felt, + model_idx: i64, + member_idx: i64, + name: &str, + ty: &Ty, + key: bool, + block_timestamp: u64, + ) -> Result<()> { + let statement = "INSERT OR IGNORE INTO model_members (id, model_id, model_idx, member_idx, name, \ + type, type_enum, enum_options, key, executed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + let enum_options = if let Ty::Enum(e) = ty { + Some(Argument::String( + e.options + .iter() + .map(|c| c.name.clone()) + .collect::>() + .join(","), + )) + } else { + None + }; + + let arguments = vec![ + Argument::String(table_id.to_string()), + Argument::String(format!("{:#x}", selector)), + Argument::Int(model_idx), + Argument::Int(member_idx), + Argument::String(name.to_string()), + Argument::String(ty.name()), + Argument::String(ty.as_ref().into()), + enum_options.unwrap_or(Argument::Null), + Argument::Bool(key), + Argument::String(utc_dt_string_from_timestamp(block_timestamp)), + ]; + + self.executor.send(QueryMessage::other(statement.to_string(), arguments))?; + + Ok(()) + } + pub async fn execute(&self) -> Result<()> { let (execute, recv) = QueryMessage::execute_recv(); self.executor.send(execute)?; From 84754b5b2858c6abf7454f707a21f98af740f180 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 25 Nov 2024 16:38:02 +0700 Subject: [PATCH 04/41] fix: model idx --- crates/torii/core/src/sql/mod.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 2bde4f905a..03f917491b 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -284,12 +284,10 @@ impl Sql { QueryType::RegisterModel, ))?; - let mut model_idx = 0_i64; self.build_model_query( selector, vec![namespaced_name.clone()], model, - model_idx, block_timestamp, upgrade_diff, )?; @@ -937,7 +935,6 @@ impl Sql { selector: Felt, path: Vec, model: &Ty, - mut model_idx: i64, block_timestamp: u64, upgrade_diff: Option<&Ty>, ) -> Result<()> { @@ -956,6 +953,7 @@ impl Sql { ); // Recursively add columns for all nested type + let mut model_idx = 0_i64; self.add_columns_recursive( &path, model, @@ -1050,6 +1048,7 @@ impl Sql { let mut new_path = path.to_vec(); new_path.push(member.name.clone()); + let model_idx = &mut (*model_idx + 1); self.add_model_member( table_id, selector, @@ -1060,8 +1059,6 @@ impl Sql { member.key, block_timestamp, )?; - - *model_idx += 1; // Increment before recursing self.add_columns_recursive( &new_path, @@ -1082,6 +1079,7 @@ impl Sql { let mut new_path = path.to_vec(); new_path.push(format!("_{}", idx)); + let model_idx = &mut (*model_idx + 1); self.add_model_member( table_id, selector, @@ -1093,8 +1091,6 @@ impl Sql { block_timestamp, )?; - *model_idx += 1; // Increment before recursing - self.add_columns_recursive( &new_path, member, @@ -1118,6 +1114,7 @@ impl Sql { add_column(&column_name, "TEXT"); + let model_idx = &mut (*model_idx + 1); self.add_model_member( table_id, selector, @@ -1128,8 +1125,6 @@ impl Sql { false, block_timestamp, )?; - - *model_idx += 1; // Increment after adding array } Ty::Enum(e) => { let column_name = if column_prefix.is_empty() { @@ -1138,6 +1133,7 @@ impl Sql { format!("{}_option", column_prefix) }; + let model_idx = &mut (*model_idx + 1); let all_options = e .options .iter() @@ -1159,8 +1155,6 @@ impl Sql { block_timestamp, )?; - *model_idx += 1; // Increment after adding enum option - for (idx, child) in e.options.iter().enumerate() { if let Ty::Tuple(tuple) = &child.ty { if tuple.is_empty() { @@ -1182,8 +1176,6 @@ impl Sql { block_timestamp, )?; - *model_idx += 1; // Increment before recursing - self.add_columns_recursive( &new_path, &child.ty, From 1ed6d286c1c0ba24addceca2a642cca61744c4f8 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 26 Nov 2024 00:22:37 +0700 Subject: [PATCH 05/41] better error log --- crates/torii/core/src/executor/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/torii/core/src/executor/mod.rs b/crates/torii/core/src/executor/mod.rs index 006d942efd..c525ccd910 100644 --- a/crates/torii/core/src/executor/mod.rs +++ b/crates/torii/core/src/executor/mod.rs @@ -756,10 +756,12 @@ impl<'c, P: Provider + Sync + Send + 'static> Executor<'c, P> { } } QueryType::Other => { - query.execute(&mut **tx).await.with_context(|| { - format!( - "Failed to execute query: {:?}, args: {:?}", - query_message.statement, query_message.arguments + query.execute(&mut **tx).await.map_err(|e| { + anyhow::anyhow!( + "Failed to execute query: {:?}, args: {:?}, error: {:?}", + query_message.statement, + query_message.arguments, + e ) })?; } From c0653a59119082f902130a2e4a7707b7e329e99b Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 26 Nov 2024 00:22:44 +0700 Subject: [PATCH 06/41] remove model idx --- crates/torii/core/src/sql/mod.rs | 72 +++++++++++--------------------- 1 file changed, 24 insertions(+), 48 deletions(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 03f917491b..b029f802cb 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -742,7 +742,11 @@ impl Sql { Ty::Enum(e) => { if e.options.iter().all( |o| { - if let Ty::Tuple(t) = &o.ty { t.is_empty() } else { false } + if let Ty::Tuple(t) = &o.ty { + t.is_empty() + } else { + false + } }, ) { return Ok(()); @@ -962,7 +966,6 @@ impl Sql { &mut indices, &table_id, selector, - &mut model_idx, block_timestamp, upgrade_diff, )?; @@ -974,12 +977,15 @@ impl Sql { // Add internal timestamps create_table_query.push_str("internal_executed_at DATETIME NOT NULL, "); - create_table_query.push_str("internal_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); - create_table_query.push_str("internal_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); + create_table_query + .push_str("internal_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); + create_table_query + .push_str("internal_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "); // Add foreign key constraints create_table_query.push_str("FOREIGN KEY (internal_entity_id) REFERENCES entities(id), "); - create_table_query.push_str("FOREIGN KEY (internal_event_message_id) REFERENCES event_messages(id));"); + create_table_query + .push_str("FOREIGN KEY (internal_event_message_id) REFERENCES event_messages(id));"); // Execute the queries if upgrade_diff.is_some() { @@ -1007,21 +1013,15 @@ impl Sql { indices: &mut Vec, table_id: &str, selector: Felt, - model_idx: &mut i64, block_timestamp: u64, upgrade_diff: Option<&Ty>, ) -> Result<()> { - let column_prefix = if path.len() > 1 { - path[1..].join("_") - } else { - String::new() - }; + let column_prefix = if path.len() > 1 { path[1..].join("_") } else { String::new() }; let mut add_column = |name: &str, sql_type: &str| { if upgrade_diff.is_some() { - alter_table_queries.push(format!( - "ALTER TABLE [{table_id}] ADD COLUMN {name} {sql_type}" - )); + alter_table_queries + .push(format!("ALTER TABLE [{table_id}] ADD COLUMN {name} {sql_type}")); } else { columns.push(format!("{name} {sql_type}")); } @@ -1048,18 +1048,16 @@ impl Sql { let mut new_path = path.to_vec(); new_path.push(member.name.clone()); - let model_idx = &mut (*model_idx + 1); self.add_model_member( table_id, selector, - *model_idx, member_idx as i64, &member.name, &member.ty, member.key, block_timestamp, )?; - + self.add_columns_recursive( &new_path, &member.ty, @@ -1068,7 +1066,6 @@ impl Sql { indices, table_id, selector, - model_idx, block_timestamp, None, )?; @@ -1079,11 +1076,9 @@ impl Sql { let mut new_path = path.to_vec(); new_path.push(format!("_{}", idx)); - let model_idx = &mut (*model_idx + 1); self.add_model_member( table_id, selector, - *model_idx, idx as i64, &format!("_{}", idx), member, @@ -1099,26 +1094,20 @@ impl Sql { indices, table_id, selector, - model_idx, block_timestamp, None, )?; } } Ty::Array(_) => { - let column_name = if column_prefix.is_empty() { - "value".to_string() - } else { - column_prefix - }; - + let column_name = + if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; + add_column(&column_name, "TEXT"); - let model_idx = &mut (*model_idx + 1); self.add_model_member( table_id, selector, - *model_idx, 0, &column_name, ty, @@ -1132,8 +1121,7 @@ impl Sql { } else { format!("{}_option", column_prefix) }; - - let model_idx = &mut (*model_idx + 1); + let all_options = e .options .iter() @@ -1147,7 +1135,6 @@ impl Sql { self.add_model_member( table_id, selector, - *model_idx, 0, &column_name, ty, @@ -1168,7 +1155,6 @@ impl Sql { self.add_model_member( table_id, selector, - *model_idx, idx as i64 + 1, &child.name, &child.ty, @@ -1184,7 +1170,6 @@ impl Sql { indices, table_id, selector, - model_idx, block_timestamp, None, )?; @@ -1192,18 +1177,14 @@ impl Sql { } _ => { if let Ok(cairo_type) = Primitive::from_str(&ty.name()) { - let column_name = if column_prefix.is_empty() { - "value".to_string() - } else { - column_prefix - }; - + let column_name = + if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; + add_column(&column_name, &cairo_type.to_sql_type().to_string()); self.add_model_member( table_id, selector, - *model_idx, 0, &column_name, ty, @@ -1221,7 +1202,6 @@ impl Sql { &mut self, table_id: &str, selector: Felt, - model_idx: i64, member_idx: i64, name: &str, ty: &Ty, @@ -1233,11 +1213,7 @@ impl Sql { let enum_options = if let Ty::Enum(e) = ty { Some(Argument::String( - e.options - .iter() - .map(|c| c.name.clone()) - .collect::>() - .join(","), + e.options.iter().map(|c| c.name.clone()).collect::>().join(","), )) } else { None @@ -1246,7 +1222,7 @@ impl Sql { let arguments = vec![ Argument::String(table_id.to_string()), Argument::String(format!("{:#x}", selector)), - Argument::Int(model_idx), + Argument::Int(0), Argument::Int(member_idx), Argument::String(name.to_string()), Argument::String(ty.name()), From 991c65762e0942203f22dd44b0f29bd58ad59a21 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 26 Nov 2024 13:40:53 +0700 Subject: [PATCH 07/41] rempve model members table --- crates/torii/core/src/sql/mod.rs | 122 +++---------------------------- 1 file changed, 12 insertions(+), 110 deletions(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index b029f802cb..a82c0249e1 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -261,10 +261,10 @@ impl Sql { let namespaced_name = get_tag(namespace, &model.name()); let insert_models = - "INSERT INTO models (id, namespace, name, class_hash, contract_address, layout, \ - packed_size, unpacked_size, executed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON \ + "INSERT INTO models (id, namespace, name, class_hash, contract_address, layout, schema, \ + packed_size, unpacked_size, executed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON \ CONFLICT(id) DO UPDATE SET contract_address=EXCLUDED.contract_address, \ - class_hash=EXCLUDED.class_hash, layout=EXCLUDED.layout, \ + class_hash=EXCLUDED.class_hash, layout=EXCLUDED.layout, schema=EXCLUDED.schema, \ packed_size=EXCLUDED.packed_size, unpacked_size=EXCLUDED.unpacked_size, \ executed_at=EXCLUDED.executed_at RETURNING *"; let arguments = vec![ @@ -274,6 +274,7 @@ impl Sql { Argument::String(format!("{class_hash:#x}")), Argument::String(format!("{contract_address:#x}")), Argument::String(serde_json::to_string(&layout)?), + Argument::String(serde_json::to_string(&model)?), Argument::Int(packed_size as i64), Argument::Int(unpacked_size as i64), Argument::String(utc_dt_string_from_timestamp(block_timestamp)), @@ -957,7 +958,6 @@ impl Sql { ); // Recursively add columns for all nested type - let mut model_idx = 0_i64; self.add_columns_recursive( &path, model, @@ -1016,23 +1016,23 @@ impl Sql { block_timestamp: u64, upgrade_diff: Option<&Ty>, ) -> Result<()> { - let column_prefix = if path.len() > 1 { path[1..].join("_") } else { String::new() }; + let column_prefix = if path.len() > 1 { path[1..].join(".") } else { String::new() }; let mut add_column = |name: &str, sql_type: &str| { if upgrade_diff.is_some() { alter_table_queries - .push(format!("ALTER TABLE [{table_id}] ADD COLUMN {name} {sql_type}")); + .push(format!("ALTER TABLE [{table_id}] ADD COLUMN [{name}] {sql_type}")); } else { - columns.push(format!("{name} {sql_type}")); + columns.push(format!("[{name}] {sql_type}")); } indices.push(format!( - "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{name}] ON [{table_id}] ({name});" + "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{name}] ON [{table_id}] ([{name}]);" )); }; match ty { Ty::Struct(s) => { - for (member_idx, member) in s.children.iter().enumerate() { + for member in &s.children { if let Some(upgrade_diff) = upgrade_diff { if !upgrade_diff .as_struct() @@ -1048,16 +1048,6 @@ impl Sql { let mut new_path = path.to_vec(); new_path.push(member.name.clone()); - self.add_model_member( - table_id, - selector, - member_idx as i64, - &member.name, - &member.ty, - member.key, - block_timestamp, - )?; - self.add_columns_recursive( &new_path, &member.ty, @@ -1076,16 +1066,6 @@ impl Sql { let mut new_path = path.to_vec(); new_path.push(format!("_{}", idx)); - self.add_model_member( - table_id, - selector, - idx as i64, - &format!("_{}", idx), - member, - false, - block_timestamp, - )?; - self.add_columns_recursive( &new_path, member, @@ -1104,22 +1084,13 @@ impl Sql { if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; add_column(&column_name, "TEXT"); - - self.add_model_member( - table_id, - selector, - 0, - &column_name, - ty, - false, - block_timestamp, - )?; } Ty::Enum(e) => { + // The variant of the enum let column_name = if column_prefix.is_empty() { "option".to_string() } else { - format!("{}_option", column_prefix) + format!("{}", column_prefix) }; let all_options = e @@ -1132,17 +1103,7 @@ impl Sql { let sql_type = format!("TEXT CHECK({} IN ({}))", column_name, all_options); add_column(&column_name, &sql_type); - self.add_model_member( - table_id, - selector, - 0, - &column_name, - ty, - false, - block_timestamp, - )?; - - for (idx, child) in e.options.iter().enumerate() { + for child in &e.options { if let Ty::Tuple(tuple) = &child.ty { if tuple.is_empty() { continue; @@ -1152,16 +1113,6 @@ impl Sql { let mut new_path = path.to_vec(); new_path.push(child.name.clone()); - self.add_model_member( - table_id, - selector, - idx as i64 + 1, - &child.name, - &child.ty, - false, - block_timestamp, - )?; - self.add_columns_recursive( &new_path, &child.ty, @@ -1181,16 +1132,6 @@ impl Sql { if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; add_column(&column_name, &cairo_type.to_sql_type().to_string()); - - self.add_model_member( - table_id, - selector, - 0, - &column_name, - ty, - false, - block_timestamp, - )?; } } } @@ -1198,45 +1139,6 @@ impl Sql { Ok(()) } - fn add_model_member( - &mut self, - table_id: &str, - selector: Felt, - member_idx: i64, - name: &str, - ty: &Ty, - key: bool, - block_timestamp: u64, - ) -> Result<()> { - let statement = "INSERT OR IGNORE INTO model_members (id, model_id, model_idx, member_idx, name, \ - type, type_enum, enum_options, key, executed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - - let enum_options = if let Ty::Enum(e) = ty { - Some(Argument::String( - e.options.iter().map(|c| c.name.clone()).collect::>().join(","), - )) - } else { - None - }; - - let arguments = vec![ - Argument::String(table_id.to_string()), - Argument::String(format!("{:#x}", selector)), - Argument::Int(0), - Argument::Int(member_idx), - Argument::String(name.to_string()), - Argument::String(ty.name()), - Argument::String(ty.as_ref().into()), - enum_options.unwrap_or(Argument::Null), - Argument::Bool(key), - Argument::String(utc_dt_string_from_timestamp(block_timestamp)), - ]; - - self.executor.send(QueryMessage::other(statement.to_string(), arguments))?; - - Ok(()) - } - pub async fn execute(&self) -> Result<()> { let (execute, recv) = QueryMessage::execute_recv(); self.executor.send(execute)?; From d8f2bcf2ec2e0a218495dc6241e643e217e1df03 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 26 Nov 2024 14:02:59 +0700 Subject: [PATCH 08/41] model schema migration --- crates/torii/migrations/20241126064130_model_schema.sql | 3 +++ .../torii/migrations/20241126064421_delete_model_members.sql | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 crates/torii/migrations/20241126064130_model_schema.sql create mode 100644 crates/torii/migrations/20241126064421_delete_model_members.sql diff --git a/crates/torii/migrations/20241126064130_model_schema.sql b/crates/torii/migrations/20241126064130_model_schema.sql new file mode 100644 index 0000000000..50965b49b5 --- /dev/null +++ b/crates/torii/migrations/20241126064130_model_schema.sql @@ -0,0 +1,3 @@ +-- Adds a new schema column to the models table. +-- The schema is the JSON serialized Ty of the model. +ALTER TABLE models ADD COLUMN schema BLOB NOT NULL; diff --git a/crates/torii/migrations/20241126064421_delete_model_members.sql b/crates/torii/migrations/20241126064421_delete_model_members.sql new file mode 100644 index 0000000000..3c3052d9ba --- /dev/null +++ b/crates/torii/migrations/20241126064421_delete_model_members.sql @@ -0,0 +1,4 @@ +-- Deletes the model_members table. Which is no longer needed since we store the schema in the models table. +PRAGMA foreign_keys = OFF; +DROP TABLE model_members; +PRAGMA foreign_keys = ON; From 1dab16586ee76e0bb17bde2e95c231dc0fccdf02 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 26 Nov 2024 14:27:09 +0700 Subject: [PATCH 09/41] lol --- crates/torii/core/src/model.rs | 674 +++---------------------------- crates/torii/core/src/sql/mod.rs | 161 +++----- 2 files changed, 91 insertions(+), 744 deletions(-) diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index 2266993e9b..ea7443669d 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -95,15 +95,12 @@ impl ModelReader for ModelSQLReader { } async fn schema(&self) -> Result { - let model_members: Vec = sqlx::query_as( - "SELECT id, model_idx, member_idx, name, type, type_enum, enum_options, key FROM \ - model_members WHERE model_id = ? ORDER BY model_idx ASC, member_idx ASC", - ) - .bind(format!("{:#x}", self.selector)) - .fetch_all(&self.pool) - .await?; + let schema: String = sqlx::query_scalar("SELECT schema FROM models WHERE id = ?") + .bind(format!("{:#x}", self.selector)) + .fetch_one(&self.pool) + .await?; - Ok(parse_sql_model_members(&self.namespace, &self.name, &model_members)) + Ok(serde_json::from_str(&schema).map_err(error::ParseError::FromJsonStr)?) } async fn packed_size(&self) -> Result { @@ -119,344 +116,82 @@ impl ModelReader for ModelSQLReader { } } -#[allow(unused)] -#[derive(Debug, sqlx::FromRow)] -pub struct SqlModelMember { - id: String, - model_idx: u32, - member_idx: u32, - name: String, - r#type: String, - type_enum: String, - enum_options: Option, - key: bool, -} - -// assume that the model members are sorted by model_idx and member_idx -// `id` is the type id of the model member -/// A helper function to parse the model members from sql table to `Ty` -pub fn parse_sql_model_members( - namespace: &str, - model: &str, - model_members_all: &[SqlModelMember], -) -> Ty { - fn parse_sql_member(member: &SqlModelMember, model_members_all: &[SqlModelMember]) -> Ty { - match member.type_enum.as_str() { - "Primitive" => Ty::Primitive(member.r#type.parse().unwrap()), - "ByteArray" => Ty::ByteArray("".to_string()), - "Struct" => { - let children = model_members_all - .iter() - .filter(|m| m.id == format!("{}${}", member.id, member.name)) - .map(|child| Member { - key: child.key, - name: child.name.to_owned(), - ty: parse_sql_member(child, model_members_all), - }) - .collect::>(); - - Ty::Struct(Struct { name: member.r#type.clone(), children }) - } - "Enum" => { - let options = member - .enum_options - .as_ref() - .expect("qed; enum_options should exist") - .split(',') - .map(|s| { - let member = if let Some(member) = model_members_all.iter().find(|m| { - m.id == format!("{}${}", member.id, member.name) && m.name == s - }) { - parse_sql_member(member, model_members_all) - } else { - Ty::Tuple(vec![]) - }; - - EnumOption { name: s.to_owned(), ty: member } - }) - .collect::>(); - - Ty::Enum(Enum { option: None, name: member.r#type.clone(), options }) - } - "Tuple" => { - let children = model_members_all - .iter() - .filter(|m| m.id == format!("{}${}", member.id, member.name)) - .map(|child| Member { - key: child.key, - name: child.name.to_owned(), - ty: parse_sql_member(child, model_members_all), - }) - .collect::>(); - - Ty::Tuple(children.into_iter().map(|m| m.ty).collect()) - } - "Array" => { - let children = model_members_all - .iter() - .filter(|m| m.id == format!("{}${}", member.id, member.name)) - .map(|child| Member { - key: child.key, - name: child.name.to_owned(), - ty: parse_sql_member(child, model_members_all), - }) - .collect::>(); - - Ty::Array(children.into_iter().map(|m| m.ty).collect()) - } - ty => { - unimplemented!("unimplemented type_enum: {ty}"); - } - } - } - - Ty::Struct(Struct { - name: get_tag(namespace, model), - children: model_members_all - .iter() - .filter(|m| m.id == get_tag(namespace, model)) - .map(|m| Member { - key: m.key, - name: m.name.to_owned(), - ty: parse_sql_member(m, model_members_all), - }) - .collect::>(), - }) - // parse_sql_model_members_impl(model, model, model_members_all) -} - /// Creates a query that fetches all models and their nested data. pub fn build_sql_query( schemas: &Vec, - entities_table: &str, - entity_relation_column: &str, + table_name: &str, where_clause: Option<&str>, - where_clause_arrays: Option<&str>, limit: Option, offset: Option, -) -> Result<(String, HashMap, String), Error> { - #[derive(Default)] - struct TableInfo { - table_name: String, - parent_table: Option, - // is_optional: bool, - depth: usize, // Track nesting depth for proper ordering - } - - #[allow(clippy::too_many_arguments)] - fn parse_ty( - path: &str, - name: &str, - ty: &Ty, - selections: &mut Vec, - tables: &mut Vec, - arrays_queries: &mut HashMap, Vec)>, - _parent_is_optional: bool, - depth: usize, - ) { - match &ty { +) -> Result<(String, String), Error> { + fn collect_columns(path: &str, ty: &Ty, selections: &mut Vec) { + match ty { Ty::Struct(s) => { - let table_name = - if path.is_empty() { name.to_string() } else { format!("{}${}", path, name) }; - - tables.push(TableInfo { - table_name: table_name.clone(), - parent_table: if path.is_empty() { None } else { Some(path.to_string()) }, - // is_optional: parent_is_optional, - depth, - }); - for child in &s.children { - parse_ty( - &table_name, - &child.name, - &child.ty, - selections, - tables, - arrays_queries, - _parent_is_optional, - depth + 1, - ); + let new_path = if path.is_empty() { + child.name.clone() + } else { + format!("{}.{}", path, child.name) + }; + collect_columns(&new_path, &child.ty, selections); } } Ty::Tuple(t) => { - let table_name = format!("{}${}", path, name); - - tables.push(TableInfo { - table_name: table_name.clone(), - parent_table: Some(path.to_string()), - // is_optional: parent_is_optional, - depth, - }); - for (i, child) in t.iter().enumerate() { - parse_ty( - &table_name, - &format!("_{}", i), - child, - selections, - tables, - arrays_queries, - _parent_is_optional, - depth + 1, - ); + let new_path = if path.is_empty() { + format!("_{}", i) + } else { + format!("{}._{}", path, i) + }; + collect_columns(&new_path, child, selections); } } - Ty::Array(t) => { - let table_name = format!("{}${}", path, name); - let is_optional = true; - - let mut array_selections = Vec::new(); - let mut array_tables = vec![TableInfo { - table_name: table_name.clone(), - parent_table: Some(path.to_string()), - // is_optional: true, - depth, - }]; - - parse_ty( - &table_name, - "data", - &t[0], - &mut array_selections, - &mut array_tables, - arrays_queries, - is_optional, - depth + 1, - ); - - arrays_queries.insert(table_name, (array_selections, array_tables)); - } Ty::Enum(e) => { - let table_name = format!("{}${}", path, name); - let is_optional = true; - - let mut is_typed = false; + // Add the enum variant column + selections.push(format!("[{}]", path)); + + // Add columns for each variant's value (if not empty tuple) for option in &e.options { if let Ty::Tuple(t) = &option.ty { if t.is_empty() { continue; } } - - parse_ty( - &table_name, - &option.name, - &option.ty, - selections, - tables, - arrays_queries, - is_optional, - depth + 1, - ); - is_typed = true; - } - - selections.push(format!("[{}].external_{} AS \"{}.{}\"", path, name, path, name)); - if is_typed { - tables.push(TableInfo { - table_name, - parent_table: Some(path.to_string()), - // is_optional: parent_is_optional || is_optional, - depth, - }); + let variant_path = format!("{}.{}", path, option.name); + collect_columns(&variant_path, &option.ty, selections); } } - _ => { - selections.push(format!("[{}].external_{} AS \"{}.{}\"", path, name, path, name)); + Ty::Array(_) | Ty::Primitive(_) | Ty::ByteArray(_) => { + selections.push(format!("[{}]", path)); } } } - let mut global_selections = Vec::new(); - let mut global_tables = Vec::new(); - let mut arrays_queries: HashMap, Vec)> = HashMap::new(); - + let mut selections = vec!["internal_id".to_string(), "internal_entity_id".to_string()]; + for model in schemas { - parse_ty( - "", - &model.name(), - model, - &mut global_selections, - &mut global_tables, - &mut arrays_queries, - false, - 0, - ); - } - - if global_tables.len() > 64 { - return Err(QueryError::SqliteJoinLimit.into()); + collect_columns("", model, &mut selections); } - // Sort tables by depth to ensure proper join order - global_tables.sort_by_key(|table| table.depth); - - let selections_clause = global_selections.join(", "); - let join_clause = global_tables - .iter() - .map(|table| { - let join_condition = - format!("{entities_table}.id = [{}].{entity_relation_column}", table.table_name); - format!(" LEFT JOIN [{}] ON {join_condition}", table.table_name) - }) - .collect::>() - .join(" "); - - let mut formatted_arrays_queries: HashMap = arrays_queries - .into_iter() - .map(|(table, (selections, mut tables))| { - let mut selections_clause = selections.join(", "); - if !selections_clause.is_empty() { - selections_clause = format!(", {}", selections_clause); - } - - // Sort array tables by depth - tables.sort_by_key(|table| table.depth); - - let join_clause = tables - .iter() - .enumerate() - .map(|(i, table)| { - if i == 0 { - format!( - " JOIN [{}] ON {entities_table}.id = [{}].{entity_relation_column}", - table.table_name, table.table_name - ) - } else { - format!( - " LEFT JOIN [{}] ON [{}].full_array_id = [{}].full_array_id", - table.table_name, - table.table_name, - table.parent_table.as_ref().unwrap() - ) - } - }) - .collect::>() - .join(" "); - - ( - table, - format!( - "SELECT {entities_table}.id, {entities_table}.keys{selections_clause} FROM \ - {entities_table}{join_clause}", - ), - ) - }) - .collect(); + let selections_clause = selections.join(", "); let mut query = format!( - "SELECT {entities_table}.id, {entities_table}.keys, {selections_clause} FROM \ - {entities_table}{join_clause}" + "SELECT {} FROM [{}]", + selections_clause, + table_name, + ); + + let mut count_query = format!( + "SELECT COUNT(internal_id) FROM [{}]", + table_name, ); - let mut count_query = - format!("SELECT COUNT({entities_table}.id) FROM {entities_table}{join_clause}"); if let Some(where_clause) = where_clause { query += &format!(" WHERE {}", where_clause); count_query += &format!(" WHERE {}", where_clause); } - query += &format!(" ORDER BY {entities_table}.event_id DESC"); + + query += " ORDER BY internal_event_id DESC"; if let Some(limit) = limit { query += &format!(" LIMIT {}", limit); @@ -466,13 +201,7 @@ pub fn build_sql_query( query += &format!(" OFFSET {}", offset); } - if let Some(where_clause_arrays) = where_clause_arrays { - for (_, formatted_query) in formatted_arrays_queries.iter_mut() { - *formatted_query = format!("{} WHERE {}", formatted_query, where_clause_arrays); - } - } - - Ok((query, formatted_arrays_queries, count_query)) + Ok((query, count_query)) } /// Populate the values of a Ty (schema) from SQLite row. @@ -658,316 +387,7 @@ pub fn map_row_to_ty( mod tests { use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; - use super::{build_sql_query, SqlModelMember}; - use crate::model::parse_sql_model_members; - - #[test] - fn parse_simple_model_members_to_ty() { - let model_members = vec![ - SqlModelMember { - id: "Test-Position".into(), - name: "x".into(), - r#type: "u256".into(), - key: false, - model_idx: 0, - member_idx: 0, - type_enum: "Primitive".into(), - enum_options: None, - }, - SqlModelMember { - id: "Test-Position".into(), - name: "y".into(), - r#type: "u256".into(), - key: false, - model_idx: 0, - member_idx: 1, - type_enum: "Primitive".into(), - enum_options: None, - }, - SqlModelMember { - id: "Test-PlayerConfig".into(), - name: "name".into(), - r#type: "ByteArray".into(), - key: false, - model_idx: 0, - member_idx: 0, - type_enum: "ByteArray".into(), - enum_options: None, - }, - ]; - - let expected_position = Ty::Struct(Struct { - name: "Test-Position".into(), - children: vec![ - dojo_types::schema::Member { - name: "x".into(), - key: false, - ty: Ty::Primitive("u256".parse().unwrap()), - }, - dojo_types::schema::Member { - name: "y".into(), - key: false, - ty: Ty::Primitive("u256".parse().unwrap()), - }, - ], - }); - - let expected_player_config = Ty::Struct(Struct { - name: "Test-PlayerConfig".into(), - children: vec![dojo_types::schema::Member { - name: "name".into(), - key: false, - ty: Ty::ByteArray("".to_string()), - }], - }); - - assert_eq!(parse_sql_model_members("Test", "Position", &model_members), expected_position); - assert_eq!( - parse_sql_model_members("Test", "PlayerConfig", &model_members), - expected_player_config - ); - } - - #[test] - fn parse_complex_model_members_to_ty() { - let model_members = vec![ - SqlModelMember { - id: "Test-Position".into(), - name: "name".into(), - r#type: "felt252".into(), - key: false, - model_idx: 0, - member_idx: 0, - type_enum: "Primitive".into(), - enum_options: None, - }, - SqlModelMember { - id: "Test-Position".into(), - name: "age".into(), - r#type: "u8".into(), - key: false, - model_idx: 0, - member_idx: 1, - type_enum: "Primitive".into(), - enum_options: None, - }, - SqlModelMember { - id: "Test-Position".into(), - name: "vec".into(), - r#type: "Vec2".into(), - key: false, - model_idx: 0, - member_idx: 1, - type_enum: "Struct".into(), - enum_options: None, - }, - SqlModelMember { - id: "Test-Position$vec".into(), - name: "x".into(), - r#type: "u256".into(), - key: false, - model_idx: 1, - member_idx: 0, - type_enum: "Primitive".into(), - enum_options: None, - }, - SqlModelMember { - id: "Test-Position$vec".into(), - name: "y".into(), - r#type: "u256".into(), - key: false, - model_idx: 1, - member_idx: 1, - type_enum: "Primitive".into(), - enum_options: None, - }, - SqlModelMember { - id: "Test-PlayerConfig".into(), - name: "favorite_item".into(), - r#type: "Option".into(), - key: false, - model_idx: 0, - member_idx: 0, - type_enum: "Enum".into(), - enum_options: Some("None,Some".into()), - }, - SqlModelMember { - id: "Test-PlayerConfig".into(), - name: "items".into(), - r#type: "Array".into(), - key: false, - model_idx: 0, - member_idx: 1, - type_enum: "Array".into(), - enum_options: None, - }, - SqlModelMember { - id: "Test-PlayerConfig$items".into(), - name: "data".into(), - r#type: "PlayerItem".into(), - key: false, - model_idx: 0, - member_idx: 1, - type_enum: "Struct".into(), - enum_options: None, - }, - SqlModelMember { - id: "Test-PlayerConfig$items$data".into(), - name: "item_id".into(), - r#type: "u32".into(), - key: false, - model_idx: 0, - member_idx: 0, - type_enum: "Primitive".into(), - enum_options: None, - }, - SqlModelMember { - id: "Test-PlayerConfig$items$data".into(), - name: "quantity".into(), - r#type: "u32".into(), - key: false, - model_idx: 0, - member_idx: 1, - type_enum: "Primitive".into(), - enum_options: None, - }, - SqlModelMember { - id: "Test-PlayerConfig$favorite_item".into(), - name: "Some".into(), - r#type: "u32".into(), - key: false, - model_idx: 1, - member_idx: 0, - type_enum: "Primitive".into(), - enum_options: None, - }, - SqlModelMember { - id: "Test-PlayerConfig$favorite_item".into(), - name: "option".into(), - r#type: "Option".into(), - key: false, - model_idx: 1, - member_idx: 0, - type_enum: "Enum".into(), - enum_options: Some("None,Some".into()), - }, - ]; - - let expected_position = Ty::Struct(Struct { - name: "Test-Position".into(), - children: vec![ - dojo_types::schema::Member { - name: "name".into(), - key: false, - ty: Ty::Primitive("felt252".parse().unwrap()), - }, - dojo_types::schema::Member { - name: "age".into(), - key: false, - ty: Ty::Primitive("u8".parse().unwrap()), - }, - dojo_types::schema::Member { - name: "vec".into(), - key: false, - ty: Ty::Struct(Struct { - name: "Vec2".into(), - children: vec![ - Member { - name: "x".into(), - key: false, - ty: Ty::Primitive("u256".parse().unwrap()), - }, - Member { - name: "y".into(), - key: false, - ty: Ty::Primitive("u256".parse().unwrap()), - }, - ], - }), - }, - ], - }); - - let expected_player_config = Ty::Struct(Struct { - name: "Test-PlayerConfig".into(), - children: vec![ - dojo_types::schema::Member { - name: "favorite_item".into(), - key: false, - ty: Ty::Enum(Enum { - name: "Option".into(), - option: None, - options: vec![ - EnumOption { name: "None".into(), ty: Ty::Tuple(vec![]) }, - EnumOption { - name: "Some".into(), - ty: Ty::Primitive("u32".parse().unwrap()), - }, - ], - }), - }, - dojo_types::schema::Member { - name: "items".into(), - key: false, - ty: Ty::Array(vec![Ty::Struct(Struct { - name: "PlayerItem".into(), - children: vec![ - Member { - name: "item_id".into(), - key: false, - ty: Ty::Primitive("u32".parse().unwrap()), - }, - Member { - name: "quantity".into(), - key: false, - ty: Ty::Primitive("u32".parse().unwrap()), - }, - ], - })]), - }, - ], - }); - - assert_eq!(parse_sql_model_members("Test", "Position", &model_members), expected_position); - assert_eq!( - parse_sql_model_members("Test", "PlayerConfig", &model_members), - expected_player_config - ); - } - - #[test] - fn parse_model_members_with_enum_to_ty() { - let model_members = vec![SqlModelMember { - id: "Test-Moves".into(), - name: "direction".into(), - r#type: "Direction".into(), - key: false, - model_idx: 0, - member_idx: 0, - type_enum: "Enum".into(), - enum_options: Some("Up,Down,Left,Right".into()), - }]; - - let expected_ty = Ty::Struct(Struct { - name: "Test-Moves".into(), - children: vec![dojo_types::schema::Member { - name: "direction".into(), - key: false, - ty: Ty::Enum(Enum { - name: "Direction".into(), - option: None, - options: vec![ - EnumOption { name: "Up".into(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Down".into(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Left".into(), ty: Ty::Tuple(vec![]) }, - EnumOption { name: "Right".into(), ty: Ty::Tuple(vec![]) }, - ], - }), - }], - }); - - assert_eq!(parse_sql_model_members("Test", "Moves", &model_members), expected_ty); - } + use super::build_sql_query; #[test] fn struct_ty_to_query() { @@ -1076,9 +496,7 @@ mod tests { let query = build_sql_query( &vec![position, player_config], "entities", - "entity_id", - None, - None, + Some("entity_id"), None, None, ) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index a82c0249e1..7ca5c60a8c 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use anyhow::{anyhow, Context, Result}; use dojo_types::naming::get_tag; use dojo_types::primitive::Primitive; -use dojo_types::schema::{EnumOption, Member, Struct, Ty}; +use dojo_types::schema::{Member, Struct, Ty}; use dojo_world::config::WorldMetadata; use dojo_world::contracts::abigen::model::Layout; use dojo_world::contracts::naming::compute_selector_from_names; @@ -374,7 +374,6 @@ impl Sql { (&entity_id, false), (&entity, keys_str.is_none()), block_timestamp, - &vec![], )?; Ok(()) @@ -436,7 +435,6 @@ impl Sql { (&entity_id, true), (&entity, false), block_timestamp, - &vec![], )?; Ok(()) @@ -620,29 +618,28 @@ impl Sql { &mut self, path: Vec, event_id: &str, - // The id of the entity and if the entity is an event message entity_id: (&str, IsEventMessage), entity: (&Ty, IsStoreUpdate), block_timestamp: u64, - indexes: &Vec, ) -> Result<()> { let (entity_id, is_event_message) = entity_id; let (entity, is_store_update_member) = entity; let update_members = |members: &[Member], - executor: &mut UnboundedSender, - indexes: &Vec| + executor: &mut UnboundedSender| -> Result<()> { - let table_id = path.join("$"); + let table_id = path[0].clone(); // Use only root path for table name + let column_prefix = if path.len() > 1 { path[1..].join(".") } else { String::new() }; + let mut columns = vec![ - "id".to_string(), - "event_id".to_string(), - "executed_at".to_string(), - "updated_at".to_string(), + "internal_id".to_string(), + "internal_event_id".to_string(), + "internal_executed_at".to_string(), + "internal_updated_at".to_string(), if is_event_message { - "event_message_id".to_string() + "internal_event_message_id".to_string() } else { - "entity_id".to_string() + "internal_entity_id".to_string() }, ]; @@ -658,62 +655,56 @@ impl Sql { Argument::String(entity_id.to_string()), ]; - if !indexes.is_empty() { - columns.push("full_array_id".to_string()); - arguments.push(Argument::String( - std::iter::once(entity_id.to_string()) - .chain(indexes.iter().map(|i| i.to_string())) - .collect::>() - .join(SQL_FELT_DELIMITER), - )); - } - - for (column_idx, idx) in indexes.iter().enumerate() { - columns.push(format!("idx_{}", column_idx)); - arguments.push(Argument::Int(*idx)); - } - for member in members.iter() { + let column_name = if column_prefix.is_empty() { + member.name.clone() + } else { + format!("{}.{}", column_prefix, member.name) + }; + match &member.ty { Ty::Primitive(ty) => { - columns.push(format!("external_{}", &member.name)); + columns.push(column_name); arguments.push(Argument::String(ty.to_sql_value())); } Ty::Enum(e) => { - columns.push(format!("external_{}", &member.name)); + // Just use the column name directly for enum option + columns.push(column_name); arguments.push(Argument::String(e.to_sql_value())); } + Ty::Array(array) => { + columns.push(column_name); + arguments.push(Argument::String(serde_json::to_string(array)?)); + } Ty::ByteArray(b) => { - columns.push(format!("external_{}", &member.name)); + columns.push(column_name); arguments.push(Argument::String(b.clone())); } - _ => {} + _ => {} // Other types are handled recursively } } let placeholders: Vec<&str> = arguments.iter().map(|_| "?").collect(); - let statement = if is_store_update_member && indexes.is_empty() { + let statement = if is_store_update_member { arguments.push(Argument::String(if is_event_message { "event:".to_string() + entity_id } else { entity_id.to_string() })); - // row has to exist. update it directly format!( - "UPDATE [{table_id}] SET {updates} WHERE id = ?", - table_id = table_id, + "UPDATE [{table_id}] SET {updates} WHERE internal_id = ?", updates = columns .iter() .zip(placeholders.iter()) - .map(|(column, placeholder)| format!("{} = {}", column, placeholder)) + .map(|(column, placeholder)| format!("[{}] = {}", column, placeholder)) .collect::>() .join(", ") ) } else { format!( - "INSERT OR REPLACE INTO [{table_id}] ({}) VALUES ({})", - columns.join(","), + "INSERT OR REPLACE INTO [{table_id}] ([{}]) VALUES ({})", + columns.join("],["), placeholders.join(",") ) }; @@ -725,7 +716,7 @@ impl Sql { match entity { Ty::Struct(s) => { - update_members(&s.children, &mut self.executor, indexes)?; + update_members(&s.children, &mut self.executor)?; for member in s.children.iter() { let mut path_clone = path.clone(); @@ -736,64 +727,41 @@ impl Sql { (entity_id, is_event_message), (&member.ty, is_store_update_member), block_timestamp, - indexes, )?; } } Ty::Enum(e) => { - if e.options.iter().all( - |o| { - if let Ty::Tuple(t) = &o.ty { - t.is_empty() - } else { - false - } - }, - ) { - return Ok(()); - } - let option = e.options[e.option.unwrap() as usize].clone(); - + update_members( &[ Member { name: "option".to_string(), ty: Ty::Enum(e.clone()), key: false }, Member { name: option.name.clone(), ty: option.ty.clone(), key: false }, ], &mut self.executor, - indexes, )?; - match &option.ty { - // Skip enum options that have no type / member - Ty::Tuple(t) if t.is_empty() => {} - _ => { - let mut path_clone = path.clone(); - path_clone.push(option.name.clone()); - self.build_set_entity_queries_recursive( - path_clone, - event_id, - (entity_id, is_event_message), - (&option.ty, is_store_update_member), - block_timestamp, - indexes, - )?; - } - } + let mut path_clone = path.clone(); + path_clone.push(option.name.clone()); + self.build_set_entity_queries_recursive( + path_clone, + event_id, + (entity_id, is_event_message), + (&option.ty, is_store_update_member), + block_timestamp, + )?; } Ty::Tuple(t) => { update_members( - t.iter() + &t.iter() .enumerate() .map(|(idx, member)| Member { name: format!("_{}", idx), ty: member.clone(), key: false, }) - .collect::>() - .as_slice(), + .collect::>(), &mut self.executor, - indexes, )?; for (idx, member) in t.iter().enumerate() { @@ -805,49 +773,10 @@ impl Sql { (entity_id, is_event_message), (member, is_store_update_member), block_timestamp, - indexes, - )?; - } - } - Ty::Array(array) => { - // delete all previous array elements with the array indexes - let table_id = path.join("$"); - let mut query = - format!("DELETE FROM [{table_id}] WHERE entity_id = ? ", table_id = table_id); - for idx in 0..indexes.len() { - query.push_str(&format!("AND idx_{} = ? ", idx)); - } - - // flatten indexes with entity id - let mut arguments = vec![Argument::String(entity_id.to_string())]; - arguments.extend(indexes.iter().map(|idx| Argument::Int(*idx))); - - self.executor.send(QueryMessage::other(query, arguments))?; - - // insert the new array elements - for (idx, member) in array.iter().enumerate() { - let mut indexes = indexes.clone(); - indexes.push(idx as i64); - - update_members( - &[Member { name: "data".to_string(), ty: member.clone(), key: false }], - &mut self.executor, - &indexes, - )?; - - let mut path_clone = path.clone(); - path_clone.push("data".to_string()); - self.build_set_entity_queries_recursive( - path_clone, - event_id, - (entity_id, is_event_message), - (member, is_store_update_member), - block_timestamp, - &indexes, )?; } } - _ => {} + _ => {} // Arrays and primitives are handled in their parent's update_members } Ok(()) From ecc459eab8bf5601e20b43b8697d7c4b4a7b459c Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 26 Nov 2024 14:44:38 +0700 Subject: [PATCH 10/41] refactor: ditch model members & refactor dynamic query --- crates/torii/core/src/model.rs | 33 +++++++----------------------- crates/torii/core/src/sql/cache.rs | 24 +++++----------------- 2 files changed, 12 insertions(+), 45 deletions(-) diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index ea7443669d..abd318a523 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -149,7 +149,7 @@ pub fn build_sql_query( Ty::Enum(e) => { // Add the enum variant column selections.push(format!("[{}]", path)); - + // Add columns for each variant's value (if not empty tuple) for option in &e.options { if let Ty::Tuple(t) = &option.ty { @@ -168,29 +168,22 @@ pub fn build_sql_query( } let mut selections = vec!["internal_id".to_string(), "internal_entity_id".to_string()]; - + for model in schemas { collect_columns("", model, &mut selections); } let selections_clause = selections.join(", "); - let mut query = format!( - "SELECT {} FROM [{}]", - selections_clause, - table_name, - ); - - let mut count_query = format!( - "SELECT COUNT(internal_id) FROM [{}]", - table_name, - ); + let mut query = format!("SELECT {} FROM [{}]", selections_clause, table_name,); + + let mut count_query = format!("SELECT COUNT(internal_id) FROM [{}]", table_name,); if let Some(where_clause) = where_clause { query += &format!(" WHERE {}", where_clause); count_query += &format!(" WHERE {}", where_clause); } - + query += " ORDER BY internal_event_id DESC"; if let Some(limit) = limit { @@ -502,19 +495,7 @@ mod tests { ) .unwrap(); - let expected_query = - "SELECT entities.id, entities.keys, [Test-Position].external_player AS \ - \"Test-Position.player\", [Test-Position$vec].external_x AS \"Test-Position$vec.x\", \ - [Test-Position$vec].external_y AS \"Test-Position$vec.y\", \ - [Test-PlayerConfig$favorite_item].external_Some AS \ - \"Test-PlayerConfig$favorite_item.Some\", [Test-PlayerConfig].external_favorite_item \ - AS \"Test-PlayerConfig.favorite_item\" FROM entities LEFT JOIN [Test-Position] ON \ - entities.id = [Test-Position].entity_id LEFT JOIN [Test-PlayerConfig] ON \ - entities.id = [Test-PlayerConfig].entity_id LEFT JOIN [Test-Position$vec] ON \ - entities.id = [Test-Position$vec].entity_id LEFT JOIN \ - [Test-PlayerConfig$favorite_item] ON entities.id = \ - [Test-PlayerConfig$favorite_item].entity_id ORDER BY entities.event_id DESC"; - // todo: completely tests arrays + let expected_query = "SELECT internal_id, internal_entity_id, [player], [vec.x], [vec.y], [test_everything], [favorite_item], [favorite_item.Some], [items] FROM [entities] WHERE entity_id ORDER BY internal_event_id DESC"; assert_eq!(query.0, expected_query); } } diff --git a/crates/torii/core/src/sql/cache.rs b/crates/torii/core/src/sql/cache.rs index 76ea4a0574..d789a42323 100644 --- a/crates/torii/core/src/sql/cache.rs +++ b/crates/torii/core/src/sql/cache.rs @@ -7,8 +7,7 @@ use starknet_crypto::Felt; use tokio::sync::RwLock; use crate::constants::TOKEN_BALANCE_TABLE; -use crate::error::{Error, ParseError, QueryError}; -use crate::model::{parse_sql_model_members, SqlModelMember}; +use crate::error::{Error, ParseError}; use crate::sql::utils::I256; use crate::types::ContractType; @@ -62,9 +61,7 @@ impl ModelCache { } async fn update_model(&self, selector: &Felt) -> Result { - let formatted_selector = format!("{:#x}", selector); - - let (namespace, name, class_hash, contract_address, packed_size, unpacked_size, layout): ( + let (namespace, name, class_hash, contract_address, packed_size, unpacked_size, layout, schema): ( String, String, String, @@ -72,9 +69,10 @@ impl ModelCache { u32, u32, String, + String, ) = sqlx::query_as( "SELECT namespace, name, class_hash, contract_address, packed_size, unpacked_size, \ - layout FROM models WHERE id = ?", + layout, schema FROM models WHERE id = ?", ) .bind(format!("{:#x}", selector)) .fetch_one(&self.pool) @@ -84,20 +82,8 @@ impl ModelCache { let contract_address = Felt::from_hex(&contract_address).map_err(ParseError::FromStr)?; let layout = serde_json::from_str(&layout).map_err(ParseError::FromJsonStr)?; + let schema = serde_json::from_str(&schema).map_err(ParseError::FromJsonStr)?; - let model_members: Vec = sqlx::query_as( - "SELECT id, model_idx, member_idx, name, type, type_enum, enum_options, key FROM \ - model_members WHERE model_id = ? ORDER BY model_idx ASC, member_idx ASC", - ) - .bind(formatted_selector) - .fetch_all(&self.pool) - .await?; - - if model_members.is_empty() { - return Err(QueryError::ModelNotFound(name.clone()).into()); - } - - let schema = parse_sql_model_members(&namespace, &name, &model_members); let mut cache = self.model_cache.write().await; let model = Model { From b8dc77f2ba8e5286bce365e3dafcd9a25da19ca8 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 26 Nov 2024 17:54:25 +0700 Subject: [PATCH 11/41] start refactoring while grpc and grahql services/type mapping --- crates/torii/core/src/model.rs | 101 +++++------ crates/torii/core/src/types.rs | 2 + crates/torii/graphql/src/object/entity.rs | 27 +-- .../torii/graphql/src/object/event_message.rs | 14 +- crates/torii/graphql/src/object/model_data.rs | 14 -- crates/torii/graphql/src/query/mod.rs | 171 ++++++------------ crates/torii/graphql/src/schema.rs | 7 +- crates/torii/grpc/src/server/mod.rs | 30 +-- 8 files changed, 141 insertions(+), 225 deletions(-) diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index abd318a523..6e3500004e 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -120,11 +120,12 @@ impl ModelReader for ModelSQLReader { pub fn build_sql_query( schemas: &Vec, table_name: &str, + entity_relation_column: &str, where_clause: Option<&str>, limit: Option, offset: Option, ) -> Result<(String, String), Error> { - fn collect_columns(path: &str, ty: &Ty, selections: &mut Vec) { + fn collect_columns(table_prefix: &str, path: &str, ty: &Ty, selections: &mut Vec) { match ty { Ty::Struct(s) => { for child in &s.children { @@ -133,22 +134,22 @@ pub fn build_sql_query( } else { format!("{}.{}", path, child.name) }; - collect_columns(&new_path, &child.ty, selections); + collect_columns(table_prefix, &new_path, &child.ty, selections); } } Ty::Tuple(t) => { for (i, child) in t.iter().enumerate() { - let new_path = if path.is_empty() { - format!("_{}", i) - } else { - format!("{}._{}", path, i) - }; - collect_columns(&new_path, child, selections); + let new_path = + if path.is_empty() { format!("{}", i) } else { format!("{}.{}", path, i) }; + collect_columns(table_prefix, &new_path, child, selections); } } Ty::Enum(e) => { - // Add the enum variant column - selections.push(format!("[{}]", path)); + // Add the enum variant column with table prefix and alias + selections.push(format!( + "{}.\"{}\" as \"{}.{}\"", + table_prefix, path, table_prefix, path + )); // Add columns for each variant's value (if not empty tuple) for option in &e.options { @@ -158,33 +159,50 @@ pub fn build_sql_query( } } let variant_path = format!("{}.{}", path, option.name); - collect_columns(&variant_path, &option.ty, selections); + collect_columns(table_prefix, &variant_path, &option.ty, selections); } } Ty::Array(_) | Ty::Primitive(_) | Ty::ByteArray(_) => { - selections.push(format!("[{}]", path)); + selections.push(format!( + "{}.\"{}\" as \"{}.{}\"", + table_prefix, path, table_prefix, path + )); } } } - let mut selections = vec!["internal_id".to_string(), "internal_entity_id".to_string()]; + let mut selections = Vec::new(); + let mut joins = Vec::new(); + // Add base table columns + selections.push(format!("{}.id", table_name)); + selections.push(format!("{}.keys", table_name)); + + // Process each model schema for model in schemas { - collect_columns("", model, &mut selections); + let model_table = model.name(); + joins.push(format!( + "LEFT JOIN [{model_table}] ON {table_name}.id = [{model_table}].{entity_relation_column}", + )); + + // Collect columns with table prefix + collect_columns(&model_table, "", model, &mut selections); } let selections_clause = selections.join(", "); + let joins_clause = joins.join(" "); - let mut query = format!("SELECT {} FROM [{}]", selections_clause, table_name,); + let mut query = format!("SELECT {} FROM [{}] {}", selections_clause, table_name, joins_clause); - let mut count_query = format!("SELECT COUNT(internal_id) FROM [{}]", table_name,); + let mut count_query = + format!("SELECT COUNT(DISTINCT {}.id) FROM [{}] {}", table_name, table_name, joins_clause); if let Some(where_clause) = where_clause { query += &format!(" WHERE {}", where_clause); count_query += &format!(" WHERE {}", where_clause); } - query += " ORDER BY internal_event_id DESC"; + query += &format!(" ORDER BY {}.event_id DESC", table_name); if let Some(limit) = limit { query += &format!(" LIMIT {}", limit); @@ -204,12 +222,12 @@ pub fn map_row_to_ty( ty: &mut Ty, // the row that contains non dynamic data for Ty row: &SqliteRow, - // a hashmap where keys are the paths for the model - // arrays and values are the rows mapping to each element - // in the array - arrays_rows: &HashMap>, ) -> Result<(), Error> { - let column_name = format!("{}.{}", path, name); + let column_name = if path.is_empty() { + name + } else { + &format!("{}.{}", path, name) + }; match ty { Ty::Primitive(primitive) => { @@ -320,52 +338,28 @@ pub fn map_row_to_ty( enum_ty.set_option(&option_name)?; } - let path = [path, name].join("$"); for option in &mut enum_ty.options { if option.name != option_name { continue; } - map_row_to_ty(&path, &option.name, &mut option.ty, row, arrays_rows)?; + map_row_to_ty(&column_name, &option.name, &mut option.ty, row)?; } } Ty::Struct(struct_ty) => { - // struct can be the main entrypoint to our model schema - // so we dont format the table name if the path is empty - let path = - if path.is_empty() { struct_ty.name.clone() } else { [path, name].join("$") }; - for member in &mut struct_ty.children { - map_row_to_ty(&path, &member.name, &mut member.ty, row, arrays_rows)?; + map_row_to_ty(&column_name, &member.name, &mut member.ty, row)?; } } Ty::Tuple(ty) => { - let path = [path, name].join("$"); - for (i, member) in ty.iter_mut().enumerate() { - map_row_to_ty(&path, &format!("_{}", i), member, row, arrays_rows)?; + map_row_to_ty(&column_name, &i.to_string(), member, row)?; } } Ty::Array(ty) => { - let path = [path, name].join("$"); - // filter by entity id in case we have multiple entities - let rows = arrays_rows - .get(&path) - .expect("qed; rows should exist") - .iter() - .filter(|array_row| array_row.get::("id") == row.get::("id")) - .collect::>(); - - // map each row to the ty of the array - let tys = rows - .iter() - .map(|row| { - let mut ty = ty[0].clone(); - map_row_to_ty(&path, "data", &mut ty, row, arrays_rows).map(|_| ty) - }) - .collect::, _>>()?; - - *ty = tys; + let serialized_array = row.try_get::(&column_name)?; + + *ty = serde_json::from_str(&serialized_array).map_err(ParseError::FromJsonStr)?; } Ty::ByteArray(bytearray) => { let value = row.try_get::(&column_name)?; @@ -489,7 +483,8 @@ mod tests { let query = build_sql_query( &vec![position, player_config], "entities", - Some("entity_id"), + "internal_entity_id", + None, None, None, ) diff --git a/crates/torii/core/src/types.rs b/crates/torii/core/src/types.rs index fef378f162..d56d33cb50 100644 --- a/crates/torii/core/src/types.rs +++ b/crates/torii/core/src/types.rs @@ -107,6 +107,8 @@ pub struct Model { pub class_hash: String, pub contract_address: String, pub transaction_hash: String, + pub layout: String, + pub schema: String, pub executed_at: DateTime, pub created_at: DateTime, } diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index 357b428979..5208eb86b7 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -5,6 +5,7 @@ use async_graphql::dynamic::{ use async_graphql::{Name, Value}; use async_recursion::async_recursion; use dojo_types::naming::get_tag; +use dojo_types::schema::Ty; use sqlx::pool::PoolConnection; use sqlx::{Pool, Sqlite}; use tokio_stream::StreamExt; @@ -19,7 +20,7 @@ use crate::constants::{ }; use crate::mapping::ENTITY_TYPE_MAPPING; use crate::object::{resolve_many, resolve_one}; -use crate::query::{type_mapping_query, value_mapping_from_row}; +use crate::query::{build_type_mapping, value_mapping_from_row}; use crate::types::TypeData; use crate::utils; #[derive(Debug)] @@ -66,8 +67,10 @@ impl ResolvableObject for EntityObject { } fn subscriptions(&self) -> Option> { - Some(vec![ - SubscriptionField::new("entityUpdated", TypeRef::named_nn(self.type_name()), |ctx| { + Some(vec![SubscriptionField::new( + "entityUpdated", + TypeRef::named_nn(self.type_name()), + |ctx| { SubscriptionFieldFuture::new(async move { let id = match ctx.args.get("id") { Some(id) => Some(id.string()?.to_string()), @@ -84,9 +87,9 @@ impl ResolvableObject for EntityObject { } })) }) - }) - .argument(InputValue::new("id", TypeRef::named(TypeRef::ID))), - ]) + }, + ) + .argument(InputValue::new("id", TypeRef::named(TypeRef::ID)))]) } } @@ -123,8 +126,8 @@ fn model_union_field() -> Field { let entity_id = utils::extract::(indexmap, "id")?; // fetch name from the models table // using the model id (hashed model name) - let model_ids: Vec<(String, String, String)> = sqlx::query_as( - "SELECT id, namespace, name + let model_ids: Vec<(String, String, String, String)> = sqlx::query_as( + "SELECT id, namespace, name, schema FROM models WHERE id IN ( SELECT model_id @@ -137,9 +140,11 @@ fn model_union_field() -> Field { .await?; let mut results: Vec> = Vec::new(); - for (id, namespace, name) in model_ids { - // the model id in the model mmeebrs table is the hashed model name (id) - let type_mapping = type_mapping_query(&mut conn, &id).await?; + for (id, namespace, name, schema) in model_ids { + let schema: Ty = serde_json::from_str(&schema).map_err(|e| { + anyhow::anyhow!(format!("Failed to parse model schema: {e}")) + })?; + let type_mapping = build_type_mapping(&namespace, &schema); // but the table name for the model data is the unhashed model name let data: ValueMapping = match model_data_recursive_query( diff --git a/crates/torii/graphql/src/object/event_message.rs b/crates/torii/graphql/src/object/event_message.rs index 2173140bd2..7f124e00eb 100644 --- a/crates/torii/graphql/src/object/event_message.rs +++ b/crates/torii/graphql/src/object/event_message.rs @@ -4,6 +4,7 @@ use async_graphql::dynamic::{ }; use async_graphql::{Name, Value}; use dojo_types::naming::get_tag; +use dojo_types::schema::Ty; use sqlx::{Pool, Sqlite}; use tokio_stream::StreamExt; use torii_core::simple_broker::SimpleBroker; @@ -18,7 +19,7 @@ use crate::constants::{ }; use crate::mapping::ENTITY_TYPE_MAPPING; use crate::object::{resolve_many, resolve_one}; -use crate::query::type_mapping_query; +use crate::query::build_type_mapping; use crate::utils; #[derive(Debug)] pub struct EventMessageObject; @@ -129,8 +130,8 @@ fn model_union_field() -> Field { let entity_id = utils::extract::(indexmap, "id")?; // fetch name from the models table // using the model id (hashed model name) - let model_ids: Vec<(String, String, String)> = sqlx::query_as( - "SELECT id, namespace, name + let model_ids: Vec<(String, String, String, String)> = sqlx::query_as( + "SELECT id, namespace, name, schema FROM models WHERE id IN ( SELECT model_id @@ -143,9 +144,10 @@ fn model_union_field() -> Field { .await?; let mut results: Vec> = Vec::new(); - for (id, namespace, name) in model_ids { - // the model id in the model mmeebrs table is the hashed model name (id) - let type_mapping = type_mapping_query(&mut conn, &id).await?; + for (id, namespace, name, schema) in model_ids { + let schema: Ty = serde_json::from_str(&schema) + .map_err(|e| anyhow::anyhow!(format!("Failed to parse model schema: {e}")))?; + let type_mapping = build_type_mapping(&namespace, &schema); // but the table name for the model data is the unhashed model name let data: ValueMapping = match model_data_recursive_query( diff --git a/crates/torii/graphql/src/object/model_data.rs b/crates/torii/graphql/src/object/model_data.rs index 689226b55c..e871a61efe 100644 --- a/crates/torii/graphql/src/object/model_data.rs +++ b/crates/torii/graphql/src/object/model_data.rs @@ -20,20 +20,6 @@ use crate::query::value_mapping_from_row; use crate::types::TypeData; use crate::utils; -#[derive(FromRow, Deserialize, PartialEq, Eq, Debug)] -pub struct ModelMember { - pub id: String, - pub model_id: String, - pub model_idx: i64, - pub name: String, - #[serde(rename = "type")] - pub ty: String, - pub type_enum: String, - pub key: bool, - pub executed_at: DateTime, - pub created_at: DateTime, -} - #[derive(Debug)] pub struct ModelDataObject { pub name: String, diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index 6586b150c9..f1226624f4 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -5,153 +5,96 @@ use async_graphql::{Name, Value}; use chrono::{DateTime, Utc}; use convert_case::{Case, Casing}; use dojo_types::primitive::{Primitive, SqlType}; +use dojo_types::schema::Ty; use regex::Regex; use sqlx::sqlite::SqliteRow; -use sqlx::{Row, SqliteConnection}; +use sqlx::Row; use torii_core::constants::SQL_FELT_DELIMITER; use crate::constants::{ BOOLEAN_TRUE, ENTITY_ID_COLUMN, EVENT_MESSAGE_ID_COLUMN, INTERNAL_ENTITY_ID_KEY, }; -use crate::object::model_data::ModelMember; use crate::types::{TypeData, TypeMapping, ValueMapping}; pub mod data; pub mod filter; pub mod order; -pub async fn type_mapping_query( - conn: &mut SqliteConnection, - model_id: &str, -) -> sqlx::Result { - let model_members = fetch_model_members(conn, model_id).await?; - let (root_members, nested_members): (Vec<&ModelMember>, Vec<&ModelMember>) = - model_members.iter().partition(|member| member.model_idx == 0); +pub fn build_type_mapping(namespace: &str, schema: &Ty) -> TypeMapping { + let model = schema.as_struct().unwrap(); - build_type_mapping(&root_members, &nested_members) -} - -async fn fetch_model_members( - conn: &mut SqliteConnection, - model_id: &str, -) -> sqlx::Result> { - sqlx::query_as( - r#" - SELECT - id, - model_id, - model_idx, - name, - type AS ty, - type_enum, - key, - executed_at, - created_at - from model_members WHERE model_id = ? - "#, - ) - .bind(model_id) - .fetch_all(conn) - .await -} - -fn build_type_mapping( - root_members: &[&ModelMember], - nested_members: &[&ModelMember], -) -> sqlx::Result { - let type_mapping: TypeMapping = root_members + model + .children .iter() - .map(|&member| { - let type_data = member_to_type_data(member, nested_members); - Ok((Name::new(&member.name), type_data)) + .map(|member| { + let type_data = member_to_type_data(namespace, &member.ty); + (Name::new(&member.name), type_data) }) - .collect::>()?; - - Ok(type_mapping) + .collect() } -fn member_to_type_data(member: &ModelMember, nested_members: &[&ModelMember]) -> TypeData { +fn member_to_type_data(namespace: &str, schema: &Ty) -> TypeData { // TODO: convert sql -> Ty directly - match member.type_enum.as_str() { - "Primitive" => TypeData::Simple(TypeRef::named(&member.ty)), - "ByteArray" => TypeData::Simple(TypeRef::named("ByteArray")), - "Array" => TypeData::List(Box::new(member_to_type_data( - nested_members - .iter() - .find(|&nested_member| { - nested_member.model_id == member.model_id - && nested_member.id.ends_with(&member.name) - // TEMP FIX: refer to parse_nested_type - && nested_member - .id - .split('$') - .collect::>() - .starts_with(&member.id.split('$').collect::>()) - }) - .expect("Array type should have nested type"), - nested_members, - ))), + match schema { + Ty::Primitive(primitive) => TypeData::Simple(TypeRef::named(primitive.to_string())), + Ty::ByteArray(_) => TypeData::Simple(TypeRef::named("ByteArray")), + Ty::Array(array) => TypeData::List(Box::new(member_to_type_data(namespace, &array[0]))), // Enums that do not have a nested member are considered as a simple Enum - "Enum" - if !nested_members.iter().any(|&nested_member| { - nested_member.model_id == member.model_id - && nested_member.id.ends_with(&member.name) - && nested_member - .id - .split('$') - .collect::>() - .starts_with(&member.id.split('$').collect::>()) + Ty::Enum(enum_) + if enum_.options.iter().all(|o| { + if let Ty::Tuple(t) = &o.ty { + t.is_empty() + } else { + false + } }) => { TypeData::Simple(TypeRef::named("Enum")) } - _ => parse_nested_type(member, nested_members), + _ => parse_nested_type(namespace, schema), } } -fn parse_nested_type(member: &ModelMember, nested_members: &[&ModelMember]) -> TypeData { - let nested_mapping: TypeMapping = nested_members - .iter() - .filter_map(|&nested_member| { - if member.model_id == nested_member.model_id - && nested_member.id.ends_with(&member.name) - // TEMP FIX: a nested member that has the same name as another nested member - // and that both have parents that start with the same id (Model$Test1 and Model$Test2) - // will end up being assigned to the wrong parent - && nested_member - .id - .split('$') - .take(nested_member.id.split('$').count() - 1) - .collect::>() - .eq(&member.id.split('$').collect::>()) - { - // if the nested member is an Enum and the member is an Enum, we need to inject the - // Enum type in order to have a "option" field in the nested Enum - // for the enum variant - if nested_member.type_enum == "Enum" - && nested_member.name == "option" - && member.type_enum == "Enum" - { - return Some((Name::new("option"), TypeData::Simple(TypeRef::named("Enum")))); - } +fn parse_nested_type(namespace: &str, schema: &Ty) -> TypeData { + let type_mapping: TypeMapping = match schema { + Ty::Struct(s) => s + .children + .iter() + .map(|member| { + let type_data = member_to_type_data(namespace, &member.ty); + (Name::new(&member.name), type_data) + }) + .collect(), + Ty::Enum(e) => { + let mut type_mapping = e + .options + .iter() + .filter_map(|option| { + // ignore unit type variants + if let Ty::Tuple(t) = &option.ty { + if t.is_empty() { + return None; + } + } + + let type_data = member_to_type_data(namespace, &option.ty); + Some((Name::new(&option.name), type_data)) + }) + .collect::(); - let type_data = member_to_type_data(nested_member, nested_members); - Some((Name::new(&nested_member.name), type_data)) - } else { - None - } - }) - .collect(); + type_mapping.insert(Name::new("option"), TypeData::Simple(TypeRef::named("Enum"))); + type_mapping + } + _ => return TypeData::Simple(TypeRef::named(schema.name())), + }; - let model_name = member.id.split('$').next().unwrap(); + let name: String = format!("{}_{}", namespace, schema.name()); // sanitizes the member type string // for eg. Position_Array -> Position_ArrayVec2 // Position_(u8, Vec2) -> Position_u8Vec2 let re = Regex::new(r"[, ()<>-]").unwrap(); - let sanitized_model_name = model_name.replace('-', "_"); - let sanitized_member_type_name = re.replace_all(&member.ty, ""); - let namespaced = format!("{}_{}", sanitized_model_name, sanitized_member_type_name); - TypeData::Nested((TypeRef::named(namespaced), nested_mapping)) + let sanitized_member_type_name = re.replace_all(&name, ""); + TypeData::Nested((TypeRef::named(sanitized_member_type_name), type_mapping)) } fn remove_hex_leading_zeros(value: Value) -> Value { diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index 79ec29e15d..95a776275e 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -1,5 +1,6 @@ use anyhow::Result; use async_graphql::dynamic::{Object, Scalar, Schema, Subscription, Union}; +use dojo_types::schema::Ty; use sqlx::SqlitePool; use torii_core::types::Model; @@ -22,7 +23,7 @@ use crate::object::metadata::MetadataObject; use crate::object::model::ModelObject; use crate::object::transaction::TransactionObject; use crate::object::ObjectVariant; -use crate::query::type_mapping_query; +use crate::query::build_type_mapping; // The graphql schema is built dynamically at runtime, this is because we won't know the schema of // the models until runtime. There are however, predefined objects such as entities and @@ -139,7 +140,9 @@ async fn build_objects(pool: &SqlitePool) -> Result<(Vec, Vec, Error> = rows .par_iter() - .map(|row| map_row_to_entity(row, &arrays_rows, &schemas, dont_include_hashed_keys)) + .map(|row| map_row_to_entity(row, &schemas, dont_include_hashed_keys)) .collect(); all_entities.extend(group_entities?); @@ -694,12 +682,11 @@ impl DojoWorld { } else { (model, format!("external_{}", member_clause.member)) }; - let (entity_query, arrays_queries, count_query) = build_sql_query( + let (entity_query, count_query) = build_sql_query( &schemas, table, entity_relation_column, Some(&format!("[{table_name}].{column_name} {comparison_operator} ?")), - None, limit, offset, )?; @@ -715,16 +702,10 @@ impl DojoWorld { .bind(offset) .fetch_all(&self.pool) .await?; - let mut arrays_rows = HashMap::new(); - for (name, query) in arrays_queries { - let rows = - sqlx::query(&query).bind(comparison_value.clone()).fetch_all(&self.pool).await?; - arrays_rows.insert(name, rows); - } let entities_collection: Result, Error> = db_entities .par_iter() - .map(|row| map_row_to_entity(row, &arrays_rows, &schemas, dont_include_hashed_keys)) + .map(|row| map_row_to_entity(row, &schemas, dont_include_hashed_keys)) .collect(); Ok((entities_collection?, total_count)) } @@ -1058,7 +1039,6 @@ fn map_row_to_event(row: &(String, String, String)) -> Result>, schemas: &[Ty], dont_include_hashed_keys: bool, ) -> Result { @@ -1067,7 +1047,7 @@ fn map_row_to_entity( .iter() .map(|schema| { let mut ty = schema.clone(); - map_row_to_ty("", &schema.name(), &mut ty, row, arrays_rows)?; + map_row_to_ty("", &schema.name(), &mut ty, row)?; Ok(ty.as_struct().unwrap().clone().into()) }) .collect::, Error>>()?; From e1c2e4f2be75f621c2e40d1e07cea9063dc95947 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 26 Nov 2024 17:59:02 +0700 Subject: [PATCH 12/41] fix set entities enum --- crates/torii/core/src/sql/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 7ca5c60a8c..315225cf5d 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -732,7 +732,12 @@ impl Sql { } Ty::Enum(e) => { let option = e.options[e.option.unwrap() as usize].clone(); - + if let Ty::Tuple(t) = &option.ty { + if t.is_empty() { + return Ok(()); + } + } + update_members( &[ Member { name: "option".to_string(), ty: Ty::Enum(e.clone()), key: false }, From cf054650740a94a67a1744c839af7c7a3ffac367 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 26 Nov 2024 19:15:01 +0700 Subject: [PATCH 13/41] fix set entities --- crates/torii/core/src/sql/mod.rs | 261 +++++++++++++------------------ 1 file changed, 108 insertions(+), 153 deletions(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 315225cf5d..0c823b9154 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -367,9 +367,8 @@ impl Sql { vec![Argument::String(entity_id.clone()), Argument::String(model_id.clone())], ))?; - let path = vec![namespaced_name]; self.build_set_entity_queries_recursive( - path, + &namespaced_name, event_id, (&entity_id, false), (&entity, keys_str.is_none()), @@ -428,9 +427,8 @@ impl Sql { }), ))?; - let path = vec![namespaced_name]; self.build_set_entity_queries_recursive( - path, + &namespaced_name, event_id, (&entity_id, true), (&entity, false), @@ -616,173 +614,130 @@ impl Sql { fn build_set_entity_queries_recursive( &mut self, - path: Vec, + model_name: &str, event_id: &str, entity_id: (&str, IsEventMessage), entity: (&Ty, IsStoreUpdate), block_timestamp: u64, ) -> Result<()> { let (entity_id, is_event_message) = entity_id; - let (entity, is_store_update_member) = entity; - - let update_members = |members: &[Member], - executor: &mut UnboundedSender| - -> Result<()> { - let table_id = path[0].clone(); // Use only root path for table name - let column_prefix = if path.len() > 1 { path[1..].join(".") } else { String::new() }; - - let mut columns = vec![ - "internal_id".to_string(), - "internal_event_id".to_string(), - "internal_executed_at".to_string(), - "internal_updated_at".to_string(), - if is_event_message { - "internal_event_message_id".to_string() - } else { - "internal_entity_id".to_string() - }, - ]; - - let mut arguments = vec![ - Argument::String(if is_event_message { - "event:".to_string() + entity_id - } else { - entity_id.to_string() - }), - Argument::String(event_id.to_string()), - Argument::String(utc_dt_string_from_timestamp(block_timestamp)), - Argument::String(chrono::Utc::now().to_rfc3339()), - Argument::String(entity_id.to_string()), - ]; + let (entity, is_store_update) = entity; + + let mut columns = vec![ + "internal_id".to_string(), + "internal_event_id".to_string(), + "internal_executed_at".to_string(), + "internal_updated_at".to_string(), + if is_event_message { + "internal_event_message_id".to_string() + } else { + "internal_entity_id".to_string() + }, + ]; - for member in members.iter() { - let column_name = if column_prefix.is_empty() { - member.name.clone() - } else { - format!("{}.{}", column_prefix, member.name) - }; + let mut arguments = vec![ + Argument::String(if is_event_message { + "event:".to_string() + entity_id + } else { + entity_id.to_string() + }), + Argument::String(event_id.to_string()), + Argument::String(utc_dt_string_from_timestamp(block_timestamp)), + Argument::String(chrono::Utc::now().to_rfc3339()), + Argument::String(entity_id.to_string()), + ]; - match &member.ty { - Ty::Primitive(ty) => { - columns.push(column_name); - arguments.push(Argument::String(ty.to_sql_value())); + fn collect_members( + prefix: &str, + ty: &Ty, + columns: &mut Vec, + arguments: &mut Vec, + ) -> Result<()> { + match ty { + Ty::Struct(s) => { + for member in &s.children { + let column_name = if prefix.is_empty() { + member.name.clone() + } else { + format!("{}.{}", prefix, member.name) + }; + collect_members(&column_name, &member.ty, columns, arguments)?; } - Ty::Enum(e) => { - // Just use the column name directly for enum option - columns.push(column_name); - arguments.push(Argument::String(e.to_sql_value())); - } - Ty::Array(array) => { - columns.push(column_name); - arguments.push(Argument::String(serde_json::to_string(array)?)); + } + Ty::Enum(e) => { + columns.push(format!("\"{}\"", prefix)); + arguments.push(Argument::String(e.to_sql_value())); + + if let Some(option_idx) = e.option { + let option = &e.options[option_idx as usize]; + if let Ty::Tuple(t) = &option.ty { + if t.is_empty() { + return Ok(()); + } + } + let variant_path = format!("{}.{}", prefix, option.name); + collect_members(&variant_path, &option.ty, columns, arguments)?; } - Ty::ByteArray(b) => { - columns.push(column_name); - arguments.push(Argument::String(b.clone())); + } + Ty::Tuple(t) => { + for (idx, member) in t.iter().enumerate() { + let column_name = if prefix.is_empty() { + format!("_{}", idx) + } else { + format!("{}._{}", prefix, idx) + }; + collect_members(&column_name, member, columns, arguments)?; } - _ => {} // Other types are handled recursively + } + Ty::Array(array) => { + columns.push(format!("\"{}\"", prefix)); + arguments.push(Argument::String(serde_json::to_string(array)?)); + } + Ty::Primitive(ty) => { + columns.push(format!("\"{}\"", prefix)); + arguments.push(Argument::String(ty.to_sql_value())); + } + Ty::ByteArray(b) => { + columns.push(format!("\"{}\"", prefix)); + arguments.push(Argument::String(b.clone())); } } - - let placeholders: Vec<&str> = arguments.iter().map(|_| "?").collect(); - let statement = if is_store_update_member { - arguments.push(Argument::String(if is_event_message { - "event:".to_string() + entity_id - } else { - entity_id.to_string() - })); - - format!( - "UPDATE [{table_id}] SET {updates} WHERE internal_id = ?", - updates = columns - .iter() - .zip(placeholders.iter()) - .map(|(column, placeholder)| format!("[{}] = {}", column, placeholder)) - .collect::>() - .join(", ") - ) - } else { - format!( - "INSERT OR REPLACE INTO [{table_id}] ([{}]) VALUES ({})", - columns.join("],["), - placeholders.join(",") - ) - }; - - executor.send(QueryMessage::other(statement, arguments))?; - Ok(()) - }; + } - match entity { - Ty::Struct(s) => { - update_members(&s.children, &mut self.executor)?; + // Collect all columns and arguments recursively + collect_members("", entity, &mut columns, &mut arguments)?; - for member in s.children.iter() { - let mut path_clone = path.clone(); - path_clone.push(member.name.clone()); - self.build_set_entity_queries_recursive( - path_clone, - event_id, - (entity_id, is_event_message), - (&member.ty, is_store_update_member), - block_timestamp, - )?; - } - } - Ty::Enum(e) => { - let option = e.options[e.option.unwrap() as usize].clone(); - if let Ty::Tuple(t) = &option.ty { - if t.is_empty() { - return Ok(()); - } - } + // Build the final query + let placeholders: Vec<&str> = arguments.iter().map(|_| "?").collect(); + let statement = if is_store_update { + arguments.push(Argument::String(if is_event_message { + "event:".to_string() + entity_id + } else { + entity_id.to_string() + })); - update_members( - &[ - Member { name: "option".to_string(), ty: Ty::Enum(e.clone()), key: false }, - Member { name: option.name.clone(), ty: option.ty.clone(), key: false }, - ], - &mut self.executor, - )?; - - let mut path_clone = path.clone(); - path_clone.push(option.name.clone()); - self.build_set_entity_queries_recursive( - path_clone, - event_id, - (entity_id, is_event_message), - (&option.ty, is_store_update_member), - block_timestamp, - )?; - } - Ty::Tuple(t) => { - update_members( - &t.iter() - .enumerate() - .map(|(idx, member)| Member { - name: format!("_{}", idx), - ty: member.clone(), - key: false, - }) - .collect::>(), - &mut self.executor, - )?; + format!( + "UPDATE [{}] SET {} WHERE internal_id = ?", + model_name, + columns + .iter() + .zip(placeholders.iter()) + .map(|(column, placeholder)| format!("{} = {}", column, placeholder)) + .collect::>() + .join(", ") + ) + } else { + format!( + "INSERT OR REPLACE INTO [{}] ({}) VALUES ({})", + model_name, + columns.join(","), + placeholders.join(",") + ) + }; - for (idx, member) in t.iter().enumerate() { - let mut path_clone = path.clone(); - path_clone.push(format!("_{}", idx)); - self.build_set_entity_queries_recursive( - path_clone, - event_id, - (entity_id, is_event_message), - (member, is_store_update_member), - block_timestamp, - )?; - } - } - _ => {} // Arrays and primitives are handled in their parent's update_members - } + // Execute the single query + self.executor.send(QueryMessage::other(statement, arguments))?; Ok(()) } From acd1daa850f030d883d86a8c3d0e8aba5185c0b6 Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 26 Nov 2024 19:15:57 +0700 Subject: [PATCH 14/41] fmt --- crates/torii/core/src/model.rs | 13 ++++++------ crates/torii/core/src/sql/cache.rs | 20 +++++++++---------- crates/torii/core/src/sql/mod.rs | 16 +++++++-------- crates/torii/graphql/src/object/entity.rs | 12 +++++------ .../torii/graphql/src/object/event_message.rs | 5 +++-- crates/torii/graphql/src/query/mod.rs | 11 ++++------ 6 files changed, 35 insertions(+), 42 deletions(-) diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index 6e3500004e..61267c0e34 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -182,7 +182,8 @@ pub fn build_sql_query( for model in schemas { let model_table = model.name(); joins.push(format!( - "LEFT JOIN [{model_table}] ON {table_name}.id = [{model_table}].{entity_relation_column}", + "LEFT JOIN [{model_table}] ON {table_name}.id = \ + [{model_table}].{entity_relation_column}", )); // Collect columns with table prefix @@ -223,11 +224,7 @@ pub fn map_row_to_ty( // the row that contains non dynamic data for Ty row: &SqliteRow, ) -> Result<(), Error> { - let column_name = if path.is_empty() { - name - } else { - &format!("{}.{}", path, name) - }; + let column_name = if path.is_empty() { name } else { &format!("{}.{}", path, name) }; match ty { Ty::Primitive(primitive) => { @@ -490,7 +487,9 @@ mod tests { ) .unwrap(); - let expected_query = "SELECT internal_id, internal_entity_id, [player], [vec.x], [vec.y], [test_everything], [favorite_item], [favorite_item.Some], [items] FROM [entities] WHERE entity_id ORDER BY internal_event_id DESC"; + let expected_query = "SELECT internal_id, internal_entity_id, [player], [vec.x], [vec.y], \ + [test_everything], [favorite_item], [favorite_item.Some], [items] \ + FROM [entities] WHERE entity_id ORDER BY internal_event_id DESC"; assert_eq!(query.0, expected_query); } } diff --git a/crates/torii/core/src/sql/cache.rs b/crates/torii/core/src/sql/cache.rs index d789a42323..3d72bd093e 100644 --- a/crates/torii/core/src/sql/cache.rs +++ b/crates/torii/core/src/sql/cache.rs @@ -61,16 +61,16 @@ impl ModelCache { } async fn update_model(&self, selector: &Felt) -> Result { - let (namespace, name, class_hash, contract_address, packed_size, unpacked_size, layout, schema): ( - String, - String, - String, - String, - u32, - u32, - String, - String, - ) = sqlx::query_as( + let ( + namespace, + name, + class_hash, + contract_address, + packed_size, + unpacked_size, + layout, + schema, + ): (String, String, String, String, u32, u32, String, String) = sqlx::query_as( "SELECT namespace, name, class_hash, contract_address, packed_size, unpacked_size, \ layout, schema FROM models WHERE id = ?", ) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 0c823b9154..f2b8186dbf 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use anyhow::{anyhow, Context, Result}; use dojo_types::naming::get_tag; use dojo_types::primitive::Primitive; -use dojo_types::schema::{Member, Struct, Ty}; +use dojo_types::schema::{Struct, Ty}; use dojo_world::config::WorldMetadata; use dojo_world::contracts::abigen::model::Layout; use dojo_world::contracts::naming::compute_selector_from_names; @@ -261,9 +261,9 @@ impl Sql { let namespaced_name = get_tag(namespace, &model.name()); let insert_models = - "INSERT INTO models (id, namespace, name, class_hash, contract_address, layout, schema, \ - packed_size, unpacked_size, executed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON \ - CONFLICT(id) DO UPDATE SET contract_address=EXCLUDED.contract_address, \ + "INSERT INTO models (id, namespace, name, class_hash, contract_address, layout, \ + schema, packed_size, unpacked_size, executed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, \ + ?) ON CONFLICT(id) DO UPDATE SET contract_address=EXCLUDED.contract_address, \ class_hash=EXCLUDED.class_hash, layout=EXCLUDED.layout, schema=EXCLUDED.schema, \ packed_size=EXCLUDED.packed_size, unpacked_size=EXCLUDED.unpacked_size, \ executed_at=EXCLUDED.executed_at RETURNING *"; @@ -839,11 +839,9 @@ impl Sql { // Start building the create table query with internal columns let mut create_table_query = format!( - "CREATE TABLE IF NOT EXISTS [{table_id}] (\ - internal_id TEXT NOT NULL PRIMARY KEY, \ - internal_event_id TEXT NOT NULL, \ - internal_entity_id TEXT, \ - internal_event_message_id TEXT, " + "CREATE TABLE IF NOT EXISTS [{table_id}] (internal_id TEXT NOT NULL PRIMARY KEY, \ + internal_event_id TEXT NOT NULL, internal_entity_id TEXT, internal_event_message_id \ + TEXT, " ); // Recursively add columns for all nested type diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index 5208eb86b7..57bce91c84 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -67,10 +67,8 @@ impl ResolvableObject for EntityObject { } fn subscriptions(&self) -> Option> { - Some(vec![SubscriptionField::new( - "entityUpdated", - TypeRef::named_nn(self.type_name()), - |ctx| { + Some(vec![ + SubscriptionField::new("entityUpdated", TypeRef::named_nn(self.type_name()), |ctx| { SubscriptionFieldFuture::new(async move { let id = match ctx.args.get("id") { Some(id) => Some(id.string()?.to_string()), @@ -87,9 +85,9 @@ impl ResolvableObject for EntityObject { } })) }) - }, - ) - .argument(InputValue::new("id", TypeRef::named(TypeRef::ID)))]) + }) + .argument(InputValue::new("id", TypeRef::named(TypeRef::ID))), + ]) } } diff --git a/crates/torii/graphql/src/object/event_message.rs b/crates/torii/graphql/src/object/event_message.rs index 7f124e00eb..fbf4b8e753 100644 --- a/crates/torii/graphql/src/object/event_message.rs +++ b/crates/torii/graphql/src/object/event_message.rs @@ -145,8 +145,9 @@ fn model_union_field() -> Field { let mut results: Vec> = Vec::new(); for (id, namespace, name, schema) in model_ids { - let schema: Ty = serde_json::from_str(&schema) - .map_err(|e| anyhow::anyhow!(format!("Failed to parse model schema: {e}")))?; + let schema: Ty = serde_json::from_str(&schema).map_err(|e| { + anyhow::anyhow!(format!("Failed to parse model schema: {e}")) + })?; let type_mapping = build_type_mapping(&namespace, &schema); // but the table name for the model data is the unhashed model name diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index f1226624f4..990bf8b616 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -41,13 +41,10 @@ fn member_to_type_data(namespace: &str, schema: &Ty) -> TypeData { Ty::Array(array) => TypeData::List(Box::new(member_to_type_data(namespace, &array[0]))), // Enums that do not have a nested member are considered as a simple Enum Ty::Enum(enum_) - if enum_.options.iter().all(|o| { - if let Ty::Tuple(t) = &o.ty { - t.is_empty() - } else { - false - } - }) => + if enum_ + .options + .iter() + .all(|o| if let Ty::Tuple(t) = &o.ty { t.is_empty() } else { false }) => { TypeData::Simple(TypeRef::named("Enum")) } From 57c3e547c6faba2e8d862ea5ffa2d13d0a99f63c Mon Sep 17 00:00:00 2001 From: Nasr Date: Tue, 26 Nov 2024 19:34:53 +0700 Subject: [PATCH 15/41] update grpc for flattened reads --- crates/torii/core/src/model.rs | 8 +++----- crates/torii/core/src/sql/mod.rs | 16 ++++++++++------ crates/torii/grpc/src/server/mod.rs | 6 ++++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index 61267c0e34..049efd879f 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -147,8 +147,7 @@ pub fn build_sql_query( Ty::Enum(e) => { // Add the enum variant column with table prefix and alias selections.push(format!( - "{}.\"{}\" as \"{}.{}\"", - table_prefix, path, table_prefix, path + "[{table_prefix}].[{path}] as \"{table_prefix}.{path}\"", )); // Add columns for each variant's value (if not empty tuple) @@ -164,8 +163,7 @@ pub fn build_sql_query( } Ty::Array(_) | Ty::Primitive(_) | Ty::ByteArray(_) => { selections.push(format!( - "{}.\"{}\" as \"{}.{}\"", - table_prefix, path, table_prefix, path + "[{table_prefix}].[{path}] as \"{table_prefix}.{path}\"", )); } } @@ -492,4 +490,4 @@ mod tests { FROM [entities] WHERE entity_id ORDER BY internal_event_id DESC"; assert_eq!(query.0, expected_query); } -} +} \ No newline at end of file diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index f2b8186dbf..0599ce708d 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -1013,13 +1013,17 @@ impl Sql { )?; } } - _ => { - if let Ok(cairo_type) = Primitive::from_str(&ty.name()) { - let column_name = - if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; + Ty::ByteArray(_) => { + let column_name = + if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; - add_column(&column_name, &cairo_type.to_sql_type().to_string()); - } + add_column(&column_name, "TEXT"); + } + Ty::Primitive(p) => { + let column_name = + if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; + + add_column(&column_name, &p.to_sql_type().to_string()); } } diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index 97b2ebd94d..00c52a3d38 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -65,11 +65,11 @@ use crate::types::ComparisonOperator; pub(crate) static ENTITIES_TABLE: &str = "entities"; pub(crate) static ENTITIES_MODEL_RELATION_TABLE: &str = "entity_model"; -pub(crate) static ENTITIES_ENTITY_RELATION_COLUMN: &str = "entity_id"; +pub(crate) static ENTITIES_ENTITY_RELATION_COLUMN: &str = "internal_entity_id"; pub(crate) static EVENT_MESSAGES_TABLE: &str = "event_messages"; pub(crate) static EVENT_MESSAGES_MODEL_RELATION_TABLE: &str = "event_model"; -pub(crate) static EVENT_MESSAGES_ENTITY_RELATION_COLUMN: &str = "event_message_id"; +pub(crate) static EVENT_MESSAGES_ENTITY_RELATION_COLUMN: &str = "internal_event_message_id"; pub(crate) static EVENT_MESSAGES_HISTORICAL_TABLE: &str = "event_messages_historical"; @@ -310,6 +310,8 @@ impl DojoWorld { None, )?; + println!("entity_query: {}", entity_query); + let rows = sqlx::query(&entity_query).bind(&models_str).fetch_all(&mut *tx).await?; let schemas = Arc::new(schemas); From eec65aa5bfd316cad1988d1b29285f4186dd999a Mon Sep 17 00:00:00 2001 From: Nasr Date: Wed, 27 Nov 2024 12:41:21 +0700 Subject: [PATCH 16/41] cleanup --- crates/torii/core/src/model.rs | 16 +++++----------- crates/torii/graphql/src/object/entity.rs | 6 +++--- crates/torii/graphql/src/object/event_message.rs | 6 +++--- crates/torii/graphql/src/object/model_data.rs | 5 ++--- .../grpc/src/server/subscriptions/entity.rs | 2 ++ 5 files changed, 15 insertions(+), 20 deletions(-) diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index 049efd879f..c389162ba8 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -1,11 +1,9 @@ -use std::collections::HashMap; use std::str::FromStr; use async_trait::async_trait; use crypto_bigint::U256; -use dojo_types::naming::get_tag; use dojo_types::primitive::Primitive; -use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; +use dojo_types::schema::Ty; use dojo_world::contracts::abigen::model::Layout; use dojo_world::contracts::model::ModelReader; use sqlx::sqlite::SqliteRow; @@ -13,7 +11,7 @@ use sqlx::{Pool, Row, Sqlite}; use starknet::core::types::Felt; use super::error::{self, Error}; -use crate::error::{ParseError, QueryError}; +use crate::error::ParseError; #[derive(Debug)] pub struct ModelSQLReader { @@ -146,9 +144,7 @@ pub fn build_sql_query( } Ty::Enum(e) => { // Add the enum variant column with table prefix and alias - selections.push(format!( - "[{table_prefix}].[{path}] as \"{table_prefix}.{path}\"", - )); + selections.push(format!("[{table_prefix}].[{path}] as \"{table_prefix}.{path}\"",)); // Add columns for each variant's value (if not empty tuple) for option in &e.options { @@ -162,9 +158,7 @@ pub fn build_sql_query( } } Ty::Array(_) | Ty::Primitive(_) | Ty::ByteArray(_) => { - selections.push(format!( - "[{table_prefix}].[{path}] as \"{table_prefix}.{path}\"", - )); + selections.push(format!("[{table_prefix}].[{path}] as \"{table_prefix}.{path}\"",)); } } } @@ -490,4 +484,4 @@ mod tests { FROM [entities] WHERE entity_id ORDER BY internal_event_id DESC"; assert_eq!(query.0, expected_query); } -} \ No newline at end of file +} diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index 57bce91c84..15ef06d46c 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -124,8 +124,8 @@ fn model_union_field() -> Field { let entity_id = utils::extract::(indexmap, "id")?; // fetch name from the models table // using the model id (hashed model name) - let model_ids: Vec<(String, String, String, String)> = sqlx::query_as( - "SELECT id, namespace, name, schema + let model_ids: Vec<(String, String, String)> = sqlx::query_as( + "SELECT namespace, name, schema FROM models WHERE id IN ( SELECT model_id @@ -138,7 +138,7 @@ fn model_union_field() -> Field { .await?; let mut results: Vec> = Vec::new(); - for (id, namespace, name, schema) in model_ids { + for (namespace, name, schema) in model_ids { let schema: Ty = serde_json::from_str(&schema).map_err(|e| { anyhow::anyhow!(format!("Failed to parse model schema: {e}")) })?; diff --git a/crates/torii/graphql/src/object/event_message.rs b/crates/torii/graphql/src/object/event_message.rs index fbf4b8e753..34508f8c1b 100644 --- a/crates/torii/graphql/src/object/event_message.rs +++ b/crates/torii/graphql/src/object/event_message.rs @@ -130,8 +130,8 @@ fn model_union_field() -> Field { let entity_id = utils::extract::(indexmap, "id")?; // fetch name from the models table // using the model id (hashed model name) - let model_ids: Vec<(String, String, String, String)> = sqlx::query_as( - "SELECT id, namespace, name, schema + let model_ids: Vec<(String, String, String)> = sqlx::query_as( + "SELECT namespace, name, schema FROM models WHERE id IN ( SELECT model_id @@ -144,7 +144,7 @@ fn model_union_field() -> Field { .await?; let mut results: Vec> = Vec::new(); - for (id, namespace, name, schema) in model_ids { + for (namespace, name, schema) in model_ids { let schema: Ty = serde_json::from_str(&schema).map_err(|e| { anyhow::anyhow!(format!("Failed to parse model schema: {e}")) })?; diff --git a/crates/torii/graphql/src/object/model_data.rs b/crates/torii/graphql/src/object/model_data.rs index e871a61efe..73e9319b70 100644 --- a/crates/torii/graphql/src/object/model_data.rs +++ b/crates/torii/graphql/src/object/model_data.rs @@ -1,9 +1,8 @@ use async_graphql::dynamic::{Enum, Field, FieldFuture, InputObject, Object, TypeRef}; use async_graphql::Value; -use chrono::{DateTime, Utc}; use dojo_types::naming::get_tag; -use serde::Deserialize; -use sqlx::{FromRow, Pool, Sqlite}; +use sqlx::Pool; +use sqlx::Sqlite; use super::connection::{connection_arguments, connection_output, parse_connection_arguments}; use super::inputs::order_input::{order_argument, parse_order_argument, OrderInputObject}; diff --git a/crates/torii/grpc/src/server/subscriptions/entity.rs b/crates/torii/grpc/src/server/subscriptions/entity.rs index f8793b5109..90837a06e4 100644 --- a/crates/torii/grpc/src/server/subscriptions/entity.rs +++ b/crates/torii/grpc/src/server/subscriptions/entity.rs @@ -113,7 +113,9 @@ impl Service { entity: &OptimisticEntity, ) -> Result<(), Error> { let mut closed_stream = Vec::new(); + println!("entity: {:?}", entity); let hashed = Felt::from_str(&entity.id).map_err(ParseError::FromStr)?; + // sometimes for some reason keys isxx empty. investigate the issue let keys = entity .keys .trim_end_matches(SQL_FELT_DELIMITER) From 985a0018aa32e92193fc7357845919f2769121c7 Mon Sep 17 00:00:00 2001 From: Nasr Date: Wed, 27 Nov 2024 16:05:23 +0700 Subject: [PATCH 17/41] feat(torii-core): delete entity new schema --- crates/torii/core/src/sql/mod.rs | 98 +++----------------------------- 1 file changed, 7 insertions(+), 91 deletions(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 0599ce708d..cdb5d087c9 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -5,7 +5,6 @@ use std::sync::Arc; use anyhow::{anyhow, Context, Result}; use dojo_types::naming::get_tag; -use dojo_types::primitive::Primitive; use dojo_types::schema::{Struct, Ty}; use dojo_world::config::WorldMetadata; use dojo_world::contracts::abigen::model::Layout; @@ -367,7 +366,7 @@ impl Sql { vec![Argument::String(entity_id.clone()), Argument::String(model_id.clone())], ))?; - self.build_set_entity_queries_recursive( + self.set_entity_model( &namespaced_name, event_id, (&entity_id, false), @@ -427,7 +426,7 @@ impl Sql { }), ))?; - self.build_set_entity_queries_recursive( + self.set_entity_model( &namespaced_name, event_id, (&entity_id, true), @@ -447,12 +446,10 @@ impl Sql { block_timestamp: u64, ) -> Result<()> { let entity_id = format!("{:#x}", entity_id); - let path = vec![entity.name()]; - // delete entity models data - self.build_delete_entity_queries_recursive(path, &entity_id, &entity)?; + let model_table = entity.name(); self.executor.send(QueryMessage::new( - "DELETE FROM entity_model WHERE entity_id = ? AND model_id = ?".to_string(), + format!("DELETE FROM [{model_table}] WHERE internal_id = ?; DELETE FROM entity_model WHERE entity_id = ? AND model_id = ?").to_string(), vec![Argument::String(entity_id.clone()), Argument::String(format!("{:#x}", model_id))], QueryType::DeleteEntity(DeleteEntityQuery { entity_id: entity_id.clone(), @@ -612,7 +609,7 @@ impl Sql { Ok(()) } - fn build_set_entity_queries_recursive( + fn set_entity_model( &mut self, model_name: &str, event_id: &str, @@ -682,9 +679,9 @@ impl Sql { Ty::Tuple(t) => { for (idx, member) in t.iter().enumerate() { let column_name = if prefix.is_empty() { - format!("_{}", idx) + format!("{}", idx) } else { - format!("{}._{}", prefix, idx) + format!("{}.{}", prefix, idx) }; collect_members(&column_name, member, columns, arguments)?; } @@ -742,87 +739,6 @@ impl Sql { Ok(()) } - fn build_delete_entity_queries_recursive( - &mut self, - path: Vec, - entity_id: &str, - entity: &Ty, - ) -> Result<()> { - match entity { - Ty::Struct(s) => { - let table_id = path.join("$"); - let statement = format!("DELETE FROM [{table_id}] WHERE entity_id = ?"); - self.executor.send(QueryMessage::other( - statement, - vec![Argument::String(entity_id.to_string())], - ))?; - for member in s.children.iter() { - 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 Ok(()); - } - - let table_id = path.join("$"); - let statement = format!("DELETE FROM [{table_id}] WHERE entity_id = ?"); - self.executor.send(QueryMessage::other( - 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.executor.send(QueryMessage::other( - 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.executor.send(QueryMessage::other( - 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)?; - } - } - _ => {} - } - - Ok(()) - } - #[allow(clippy::too_many_arguments)] fn build_model_query( &mut self, From 5233b5ff80c41cbb63c68897d814bdc3ca820e05 Mon Sep 17 00:00:00 2001 From: Nasr Date: Fri, 29 Nov 2024 15:15:27 +0700 Subject: [PATCH 18/41] graphql integration almost done --- crates/torii/graphql/src/constants.rs | 4 +- .../graphql/src/object/connection/mod.rs | 4 +- crates/torii/graphql/src/object/entity.rs | 150 ++---------------- .../torii/graphql/src/object/event_message.rs | 38 ++--- crates/torii/graphql/src/object/mod.rs | 4 +- crates/torii/graphql/src/object/model_data.rs | 10 +- crates/torii/graphql/src/query/mod.rs | 84 +++++++--- crates/torii/graphql/src/schema.rs | 1 + .../grpc/src/server/subscriptions/entity.rs | 1 - 9 files changed, 108 insertions(+), 188 deletions(-) diff --git a/crates/torii/graphql/src/constants.rs b/crates/torii/graphql/src/constants.rs index f309b82b91..fc7d7c0285 100644 --- a/crates/torii/graphql/src/constants.rs +++ b/crates/torii/graphql/src/constants.rs @@ -12,8 +12,8 @@ pub const METADATA_TABLE: &str = "metadata"; pub const ID_COLUMN: &str = "id"; pub const EVENT_ID_COLUMN: &str = "event_id"; -pub const ENTITY_ID_COLUMN: &str = "entity_id"; -pub const EVENT_MESSAGE_ID_COLUMN: &str = "event_message_id"; +pub const ENTITY_ID_COLUMN: &str = "internal_entity_id"; +pub const EVENT_MESSAGE_ID_COLUMN: &str = "internal_event_message_id"; pub const JSON_COLUMN: &str = "json"; pub const TRANSACTION_HASH_COLUMN: &str = "transaction_hash"; diff --git a/crates/torii/graphql/src/object/connection/mod.rs b/crates/torii/graphql/src/object/connection/mod.rs index dc9931ac58..d979106333 100644 --- a/crates/torii/graphql/src/object/connection/mod.rs +++ b/crates/torii/graphql/src/object/connection/mod.rs @@ -125,8 +125,8 @@ pub fn connection_output( .map(|row| { let order_field = match order { Some(order) => { - if is_external { - format!("external_{}", order.field) + if !is_external { + format!("internal_{}", order.field) } else { order.field.to_string() } diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index 15ef06d46c..ddd0bfb622 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -67,8 +67,10 @@ impl ResolvableObject for EntityObject { } fn subscriptions(&self) -> Option> { - Some(vec![ - SubscriptionField::new("entityUpdated", TypeRef::named_nn(self.type_name()), |ctx| { + Some(vec![SubscriptionField::new( + "entityUpdated", + TypeRef::named_nn(self.type_name()), + |ctx| { SubscriptionFieldFuture::new(async move { let id = match ctx.args.get("id") { Some(id) => Some(id.string()?.to_string()), @@ -85,9 +87,9 @@ impl ResolvableObject for EntityObject { } })) }) - }) - .argument(InputValue::new("id", TypeRef::named(TypeRef::ID))), - ]) + }, + ) + .argument(InputValue::new("id", TypeRef::named(TypeRef::ID)))]) } } @@ -144,21 +146,16 @@ fn model_union_field() -> Field { })?; let type_mapping = build_type_mapping(&namespace, &schema); - // but the table name for the model data is the unhashed model name - let data: ValueMapping = match model_data_recursive_query( - &mut conn, - ENTITY_ID_COLUMN, - vec![get_tag(&namespace, &name)], - &entity_id, - &[], - &type_mapping, - false, - ) - .await? - { - Value::Object(map) => map, - _ => unreachable!(), - }; + // Get the table name + let table_name = get_tag(&namespace, &name); + + // Fetch the row data + let query = format!("SELECT * FROM [{}] WHERE internal_entity_id = ?", table_name); + let row = + sqlx::query(&query).bind(&entity_id).fetch_one(&mut *conn).await?; + + // Use value_mapping_from_row to handle nested structures + let data = value_mapping_from_row(&row, &type_mapping, true)?; results.push(FieldValue::with_type( FieldValue::owned_any(data), @@ -173,116 +170,3 @@ fn model_union_field() -> Field { }) }) } - -// TODO: flatten query -#[async_recursion] -pub async fn model_data_recursive_query( - conn: &mut PoolConnection, - entity_id_column: &str, - path_array: Vec, - entity_id: &str, - indexes: &[i64], - type_mapping: &TypeMapping, - is_list: bool, -) -> sqlx::Result { - // For nested types, we need to remove prefix in path array - let namespace = format!("{}_", path_array[0]); - let table_name = &path_array.join("$").replace(&namespace, ""); - let mut query = - format!("SELECT * FROM [{}] WHERE {entity_id_column} = '{}' ", table_name, entity_id); - for (column_idx, index) in indexes.iter().enumerate() { - query.push_str(&format!("AND idx_{} = {} ", column_idx, index)); - } - - let rows = sqlx::query(&query).fetch_all(conn.as_mut()).await?; - if rows.is_empty() { - return Ok(Value::List(vec![])); - } - - let value_mapping: Value; - let mut nested_value_mappings = Vec::new(); - - for (idx, row) in rows.iter().enumerate() { - let mut nested_value_mapping = value_mapping_from_row(row, type_mapping, true)?; - - for (field_name, type_data) in type_mapping { - if let TypeData::Nested((_, nested_mapping)) = type_data { - let mut nested_path = path_array.clone(); - nested_path.push(field_name.to_string()); - - let nested_values = model_data_recursive_query( - conn, - entity_id_column, - nested_path, - entity_id, - &if is_list { - let mut indexes = indexes.to_vec(); - indexes.push(idx as i64); - indexes - } else { - indexes.to_vec() - }, - nested_mapping, - false, - ) - .await?; - - nested_value_mapping.insert(Name::new(field_name), nested_values); - } else if let TypeData::List(inner) = type_data { - let mut nested_path = path_array.clone(); - nested_path.push(field_name.to_string()); - - let data = match model_data_recursive_query( - conn, - entity_id_column, - nested_path, - entity_id, - // this might need to be changed to support 2d+ arrays - &if is_list { - let mut indexes = indexes.to_vec(); - indexes.push(idx as i64); - indexes - } else { - indexes.to_vec() - }, - &IndexMap::from([(Name::new("data"), *inner.clone())]), - true, - ) - .await? - { - // map our list which uses a data field as a place holder - // for all elements to get the elemnt directly - Value::List(data) => data - .iter() - .map(|v| match v { - Value::Object(map) => map.get(&Name::new("data")).unwrap().clone(), - ty => unreachable!( - "Expected Value::Object for list \"data\" field, got {:?}", - ty - ), - }) - .collect(), - Value::Object(map) => map.get(&Name::new("data")).unwrap().clone(), - ty => { - unreachable!( - "Expected Value::List or Value::Object for list, got {:?}", - ty - ); - } - }; - - nested_value_mapping.insert(Name::new(field_name), data); - } - } - - nested_value_mappings.push(Value::Object(nested_value_mapping)); - } - - if is_list { - value_mapping = Value::List(nested_value_mappings); - } else { - value_mapping = nested_value_mappings.pop().unwrap(); - } - - Ok(value_mapping) -} diff --git a/crates/torii/graphql/src/object/event_message.rs b/crates/torii/graphql/src/object/event_message.rs index 34508f8c1b..945bde5bb8 100644 --- a/crates/torii/graphql/src/object/event_message.rs +++ b/crates/torii/graphql/src/object/event_message.rs @@ -10,7 +10,6 @@ use tokio_stream::StreamExt; use torii_core::simple_broker::SimpleBroker; use torii_core::types::EventMessage; -use super::entity::model_data_recursive_query; use super::inputs::keys_input::keys_argument; use super::{BasicObject, ResolvableObject, TypeMapping, ValueMapping}; use crate::constants::{ @@ -19,8 +18,9 @@ use crate::constants::{ }; use crate::mapping::ENTITY_TYPE_MAPPING; use crate::object::{resolve_many, resolve_one}; -use crate::query::build_type_mapping; +use crate::query::{build_type_mapping, value_mapping_from_row}; use crate::utils; + #[derive(Debug)] pub struct EventMessageObject; @@ -75,8 +75,6 @@ impl ResolvableObject for EventMessageObject { Some(id) => Some(id.string()?.to_string()), None => None, }; - // if id is None, then subscribe to all entities - // if id is Some, then subscribe to only the entity with that id Ok(SimpleBroker::::subscribe().filter_map( move |entity: EventMessage| { if id.is_none() || id == Some(entity.id.clone()) { @@ -84,7 +82,6 @@ impl ResolvableObject for EventMessageObject { entity, )))) } else { - // id != entity.id , then don't send anything, still listening None } }, @@ -129,7 +126,6 @@ fn model_union_field() -> Field { let entity_id = utils::extract::(indexmap, "id")?; // fetch name from the models table - // using the model id (hashed model name) let model_ids: Vec<(String, String, String)> = sqlx::query_as( "SELECT namespace, name, schema FROM models @@ -150,21 +146,21 @@ fn model_union_field() -> Field { })?; let type_mapping = build_type_mapping(&namespace, &schema); - // but the table name for the model data is the unhashed model name - let data: ValueMapping = match model_data_recursive_query( - &mut conn, - EVENT_MESSAGE_ID_COLUMN, - vec![get_tag(&namespace, &name)], - &entity_id, - &[], - &type_mapping, - false, - ) - .await? - { - Value::Object(map) => map, - _ => unreachable!(), - }; + // Get the table name + let table_name = get_tag(&namespace, &name); + + // Fetch the row data + let query = format!( + "SELECT * FROM [{}] WHERE internal_event_message_id = ?", + table_name + ); + let row = sqlx::query(&query) + .bind(&entity_id) + .fetch_one(&mut *conn) + .await?; + + // Use value_mapping_from_row to handle nested structures + let data = value_mapping_from_row(&row, &type_mapping, true)?; results.push(FieldValue::with_type( FieldValue::owned_any(data), diff --git a/crates/torii/graphql/src/object/mod.rs b/crates/torii/graphql/src/object/mod.rs index b4201e0989..b2a8d4063e 100644 --- a/crates/torii/graphql/src/object/mod.rs +++ b/crates/torii/graphql/src/object/mod.rs @@ -261,7 +261,7 @@ pub fn resolve_one( let id: String = extract::(ctx.args.as_index_map(), &id_column.to_case(Case::Camel))?; let data = fetch_single_row(&mut conn, &table_name, &id_column, &id).await?; - let model = value_mapping_from_row(&data, &type_mapping, false)?; + let model = value_mapping_from_row(&data, &type_mapping, true)?; Ok(Some(Value::Object(model))) }) }) @@ -310,7 +310,7 @@ pub fn resolve_many( &order, &id_column, total_count, - false, + true, page_info, )?; diff --git a/crates/torii/graphql/src/object/model_data.rs b/crates/torii/graphql/src/object/model_data.rs index 73e9319b70..3d93ea031a 100644 --- a/crates/torii/graphql/src/object/model_data.rs +++ b/crates/torii/graphql/src/object/model_data.rs @@ -1,6 +1,7 @@ use async_graphql::dynamic::{Enum, Field, FieldFuture, InputObject, Object, TypeRef}; use async_graphql::Value; use dojo_types::naming::get_tag; +use dojo_types::schema::Ty; use sqlx::Pool; use sqlx::Sqlite; @@ -25,16 +26,17 @@ pub struct ModelDataObject { pub plural_name: String, pub type_name: String, pub type_mapping: TypeMapping, + pub schema: Ty, pub where_input: WhereInputObject, pub order_input: OrderInputObject, } impl ModelDataObject { - pub fn new(name: String, type_name: String, type_mapping: TypeMapping) -> Self { + pub fn new(name: String, type_name: String, type_mapping: TypeMapping, schema: Ty) -> Self { let where_input = WhereInputObject::new(type_name.as_str(), &type_mapping); let order_input = OrderInputObject::new(type_name.as_str(), &type_mapping); let plural_name = format!("{}Models", name); - Self { name, plural_name, type_name, type_mapping, where_input, order_input } + Self { name, plural_name, type_name, type_mapping, schema, where_input, order_input } } } @@ -104,7 +106,7 @@ impl ResolvableObject for ModelDataObject { let (data, page_info) = fetch_multiple_rows( &mut conn, &table_name, - EVENT_ID_COLUMN, + "internal_event_id", &None, &order, &filters, @@ -116,7 +118,7 @@ impl ResolvableObject for ModelDataObject { &data, &type_mapping, &order, - EVENT_ID_COLUMN, + "internal_event_id", total_count, true, page_info, diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index 990bf8b616..49e8b3e954 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -112,29 +112,61 @@ pub fn value_mapping_from_row( types: &TypeMapping, is_external: bool, ) -> sqlx::Result { - let mut value_mapping = types - .iter() - .filter(|(_, type_data)| { - type_data.is_simple() - // ignore Enum fields because the column is not stored in this row. we inejct it later - // && !(type_data.type_ref().to_string() == "Enum") - }) - .map(|(field_name, type_data)| { - let mut value = - fetch_value(row, field_name, &type_data.type_ref().to_string(), is_external)?; - - // handles felt arrays stored as string (ex: keys) - if let (TypeRef::List(_), Value::String(s)) = (&type_data.type_ref(), &value) { - let mut felts: Vec<_> = s.split(SQL_FELT_DELIMITER).map(Value::from).collect(); - felts.pop(); // removes empty item - value = Value::List(felts); + println!("types: {:?}", types); + fn build_value_mapping( + row: &SqliteRow, + types: &TypeMapping, + prefix: &str, + is_external: bool, + ) -> sqlx::Result { + let mut value_mapping = ValueMapping::new(); + + for (field_name, type_data) in types { + let column_name = if prefix.is_empty() { + field_name.to_string() + } else { + format!("{}.{}", prefix, field_name) + }; + + match type_data { + TypeData::Simple(type_ref) => { + let mut value = fetch_value(row, &column_name, &type_ref.to_string(), is_external)?; + + // handles felt arrays stored as string (ex: keys) + if let (TypeRef::List(_), Value::String(s)) = (type_ref, &value) { + let mut felts: Vec<_> = s.split(SQL_FELT_DELIMITER).map(Value::from).collect(); + felts.pop(); // removes empty item + value = Value::List(felts); + } + + value_mapping.insert(Name::new(field_name), value); + } + TypeData::List(inner) => { + let value = fetch_value(row, &column_name, "String", is_external)?; + if let Value::String(json_str) = value { + let array_value: Value = serde_json::from_str(&json_str) + .map_err(|e| sqlx::Error::Protocol(format!("JSON parse error: {}", e)))?; + value_mapping.insert(Name::new(field_name), array_value); + } + } + TypeData::Nested((_, nested_mapping)) => { + let nested_values = build_value_mapping( + row, + nested_mapping, + &column_name, + is_external, + )?; + value_mapping.insert(Name::new(field_name), Value::Object(nested_values)); + } } + } - Ok((Name::new(field_name), value)) - }) - .collect::>()?; + Ok(value_mapping) + } + + let mut value_mapping = build_value_mapping(row, types, "", is_external)?; - // entity_id is not part of a model's type_mapping but needed to relate to parent entity + // Add internal entity ID if present if let Ok(entity_id) = row.try_get::(ENTITY_ID_COLUMN) { value_mapping.insert(Name::new(INTERNAL_ENTITY_ID_KEY), Value::from(entity_id)); } else if let Ok(event_message_id) = row.try_get::(EVENT_MESSAGE_ID_COLUMN) { @@ -150,12 +182,18 @@ fn fetch_value( type_name: &str, is_external: bool, ) -> sqlx::Result { - let column_name = if is_external { - format!("external_{}", field_name) + let mut column_name = if !is_external { + format!("internal_{}", field_name) } else { - field_name.to_string().to_case(Case::Snake) + field_name.to_string() }; + // for enum options, remove the ".option" suffix to get the variant + // through the enum itself field name + if type_name == "Enum" && column_name.ends_with(".option") { + column_name = column_name.trim_end_matches(".option").to_string(); + } + match Primitive::from_str(type_name) { // fetch boolean Ok(Primitive::Bool(_)) => { diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index 95a776275e..8e1aba65c3 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -156,6 +156,7 @@ async fn build_objects(pool: &SqlitePool) -> Result<(Vec, Vec Result<(), Error> { let mut closed_stream = Vec::new(); - println!("entity: {:?}", entity); let hashed = Felt::from_str(&entity.id).map_err(ParseError::FromStr)?; // sometimes for some reason keys isxx empty. investigate the issue let keys = entity From 1de3f01b226bba46ec9f05ddd0d702d428f83825 Mon Sep 17 00:00:00 2001 From: Nasr Date: Fri, 29 Nov 2024 15:29:22 +0700 Subject: [PATCH 19/41] global arrays refactor & fix graphql --- crates/torii/core/src/model.rs | 15 +++++++++++++-- crates/torii/core/src/sql/mod.rs | 4 +++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index c389162ba8..3e7126a95d 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -2,10 +2,11 @@ use std::str::FromStr; use async_trait::async_trait; use crypto_bigint::U256; -use dojo_types::primitive::Primitive; +use dojo_types::primitive::{Primitive, PrimitiveError}; use dojo_types::schema::Ty; use dojo_world::contracts::abigen::model::Layout; use dojo_world::contracts::model::ModelReader; +use serde_json::Value as JsonValue; use sqlx::sqlite::SqliteRow; use sqlx::{Pool, Row, Sqlite}; use starknet::core::types::Felt; @@ -346,9 +347,19 @@ pub fn map_row_to_ty( } } Ty::Array(ty) => { + let schema = ty[0].clone(); let serialized_array = row.try_get::(&column_name)?; - *ty = serde_json::from_str(&serialized_array).map_err(ParseError::FromJsonStr)?; + let values: Vec = + serde_json::from_str(&serialized_array).map_err(ParseError::FromJsonStr)?; + *ty = values + .iter() + .map(|v| { + let mut ty = schema.clone(); + ty.from_json_value(v.clone())?; + Result::<_, PrimitiveError>::Ok(ty) + }) + .collect::, _>>()?; } Ty::ByteArray(bytearray) => { let value = row.try_get::(&column_name)?; diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index cdb5d087c9..d391bd3e11 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -688,7 +688,9 @@ impl Sql { } Ty::Array(array) => { columns.push(format!("\"{}\"", prefix)); - arguments.push(Argument::String(serde_json::to_string(array)?)); + let values = + array.iter().map(|v| v.to_json_value()).collect::, _>>()?; + arguments.push(Argument::String(serde_json::to_string(&values)?)); } Ty::Primitive(ty) => { columns.push(format!("\"{}\"", prefix)); From a8afbcde2463fed5b2dc6edc1d469a5eb04e464d Mon Sep 17 00:00:00 2001 From: Nasr Date: Fri, 29 Nov 2024 15:32:07 +0700 Subject: [PATCH 20/41] fmt --- crates/torii/core/src/model.rs | 4 +-- crates/torii/core/src/sql/mod.rs | 6 ++++- crates/torii/graphql/src/object/entity.rs | 21 ++++++--------- .../torii/graphql/src/object/event_message.rs | 12 ++++----- crates/torii/graphql/src/object/model_data.rs | 7 +++-- crates/torii/graphql/src/query/mod.rs | 27 ++++++++----------- 6 files changed, 34 insertions(+), 43 deletions(-) diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index 3e7126a95d..411abaebf3 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -358,8 +358,8 @@ pub fn map_row_to_ty( let mut ty = schema.clone(); ty.from_json_value(v.clone())?; Result::<_, PrimitiveError>::Ok(ty) - }) - .collect::, _>>()?; + }) + .collect::, _>>()?; } Ty::ByteArray(bytearray) => { let value = row.try_get::(&column_name)?; diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index d391bd3e11..7d547d4f7d 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -449,7 +449,11 @@ impl Sql { let model_table = entity.name(); self.executor.send(QueryMessage::new( - format!("DELETE FROM [{model_table}] WHERE internal_id = ?; DELETE FROM entity_model WHERE entity_id = ? AND model_id = ?").to_string(), + format!( + "DELETE FROM [{model_table}] WHERE internal_id = ?; DELETE FROM entity_model \ + WHERE entity_id = ? AND model_id = ?" + ) + .to_string(), vec![Argument::String(entity_id.clone()), Argument::String(format!("{:#x}", model_id))], QueryType::DeleteEntity(DeleteEntityQuery { entity_id: entity_id.clone(), diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index ddd0bfb622..3882413380 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -3,10 +3,8 @@ use async_graphql::dynamic::{ Field, FieldFuture, FieldValue, InputValue, SubscriptionField, SubscriptionFieldFuture, TypeRef, }; use async_graphql::{Name, Value}; -use async_recursion::async_recursion; use dojo_types::naming::get_tag; use dojo_types::schema::Ty; -use sqlx::pool::PoolConnection; use sqlx::{Pool, Sqlite}; use tokio_stream::StreamExt; use torii_core::simple_broker::SimpleBroker; @@ -15,13 +13,11 @@ use torii_core::types::Entity; use super::inputs::keys_input::keys_argument; use super::{BasicObject, ResolvableObject, TypeMapping, ValueMapping}; use crate::constants::{ - DATETIME_FORMAT, ENTITY_ID_COLUMN, ENTITY_NAMES, ENTITY_TABLE, ENTITY_TYPE_NAME, - EVENT_ID_COLUMN, ID_COLUMN, + DATETIME_FORMAT, ENTITY_NAMES, ENTITY_TABLE, ENTITY_TYPE_NAME, EVENT_ID_COLUMN, ID_COLUMN, }; use crate::mapping::ENTITY_TYPE_MAPPING; use crate::object::{resolve_many, resolve_one}; use crate::query::{build_type_mapping, value_mapping_from_row}; -use crate::types::TypeData; use crate::utils; #[derive(Debug)] pub struct EntityObject; @@ -67,10 +63,8 @@ impl ResolvableObject for EntityObject { } fn subscriptions(&self) -> Option> { - Some(vec![SubscriptionField::new( - "entityUpdated", - TypeRef::named_nn(self.type_name()), - |ctx| { + Some(vec![ + SubscriptionField::new("entityUpdated", TypeRef::named_nn(self.type_name()), |ctx| { SubscriptionFieldFuture::new(async move { let id = match ctx.args.get("id") { Some(id) => Some(id.string()?.to_string()), @@ -87,9 +81,9 @@ impl ResolvableObject for EntityObject { } })) }) - }, - ) - .argument(InputValue::new("id", TypeRef::named(TypeRef::ID)))]) + }) + .argument(InputValue::new("id", TypeRef::named(TypeRef::ID))), + ]) } } @@ -150,7 +144,8 @@ fn model_union_field() -> Field { let table_name = get_tag(&namespace, &name); // Fetch the row data - let query = format!("SELECT * FROM [{}] WHERE internal_entity_id = ?", table_name); + let query = + format!("SELECT * FROM [{}] WHERE internal_entity_id = ?", table_name); let row = sqlx::query(&query).bind(&entity_id).fetch_one(&mut *conn).await?; diff --git a/crates/torii/graphql/src/object/event_message.rs b/crates/torii/graphql/src/object/event_message.rs index 945bde5bb8..c704e9ad5a 100644 --- a/crates/torii/graphql/src/object/event_message.rs +++ b/crates/torii/graphql/src/object/event_message.rs @@ -13,8 +13,8 @@ use torii_core::types::EventMessage; use super::inputs::keys_input::keys_argument; use super::{BasicObject, ResolvableObject, TypeMapping, ValueMapping}; use crate::constants::{ - DATETIME_FORMAT, EVENT_ID_COLUMN, EVENT_MESSAGE_ID_COLUMN, EVENT_MESSAGE_NAMES, - EVENT_MESSAGE_TABLE, EVENT_MESSAGE_TYPE_NAME, ID_COLUMN, + DATETIME_FORMAT, EVENT_ID_COLUMN, EVENT_MESSAGE_NAMES, EVENT_MESSAGE_TABLE, + EVENT_MESSAGE_TYPE_NAME, ID_COLUMN, }; use crate::mapping::ENTITY_TYPE_MAPPING; use crate::object::{resolve_many, resolve_one}; @@ -148,16 +148,14 @@ fn model_union_field() -> Field { // Get the table name let table_name = get_tag(&namespace, &name); - + // Fetch the row data let query = format!( "SELECT * FROM [{}] WHERE internal_event_message_id = ?", table_name ); - let row = sqlx::query(&query) - .bind(&entity_id) - .fetch_one(&mut *conn) - .await?; + let row = + sqlx::query(&query).bind(&entity_id).fetch_one(&mut *conn).await?; // Use value_mapping_from_row to handle nested structures let data = value_mapping_from_row(&row, &type_mapping, true)?; diff --git a/crates/torii/graphql/src/object/model_data.rs b/crates/torii/graphql/src/object/model_data.rs index 3d93ea031a..5afd6b0dac 100644 --- a/crates/torii/graphql/src/object/model_data.rs +++ b/crates/torii/graphql/src/object/model_data.rs @@ -2,8 +2,7 @@ use async_graphql::dynamic::{Enum, Field, FieldFuture, InputObject, Object, Type use async_graphql::Value; use dojo_types::naming::get_tag; use dojo_types::schema::Ty; -use sqlx::Pool; -use sqlx::Sqlite; +use sqlx::{Pool, Sqlite}; use super::connection::{connection_arguments, connection_output, parse_connection_arguments}; use super::inputs::order_input::{order_argument, parse_order_argument, OrderInputObject}; @@ -11,8 +10,8 @@ use super::inputs::where_input::{parse_where_argument, where_argument, WhereInpu use super::inputs::InputObjectTrait; use super::{BasicObject, ResolvableObject, TypeMapping, ValueMapping}; use crate::constants::{ - ENTITY_ID_COLUMN, ENTITY_TABLE, ENTITY_TYPE_NAME, EVENT_ID_COLUMN, EVENT_MESSAGE_TABLE, - EVENT_MESSAGE_TYPE_NAME, ID_COLUMN, INTERNAL_ENTITY_ID_KEY, + ENTITY_ID_COLUMN, ENTITY_TABLE, ENTITY_TYPE_NAME, EVENT_MESSAGE_TABLE, EVENT_MESSAGE_TYPE_NAME, + ID_COLUMN, INTERNAL_ENTITY_ID_KEY, }; use crate::mapping::ENTITY_TYPE_MAPPING; use crate::query::data::{count_rows, fetch_multiple_rows, fetch_single_row}; diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index 49e8b3e954..41c1407734 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -3,7 +3,6 @@ use std::str::FromStr; use async_graphql::dynamic::TypeRef; use async_graphql::{Name, Value}; use chrono::{DateTime, Utc}; -use convert_case::{Case, Casing}; use dojo_types::primitive::{Primitive, SqlType}; use dojo_types::schema::Ty; use regex::Regex; @@ -130,11 +129,13 @@ pub fn value_mapping_from_row( match type_data { TypeData::Simple(type_ref) => { - let mut value = fetch_value(row, &column_name, &type_ref.to_string(), is_external)?; + let mut value = + fetch_value(row, &column_name, &type_ref.to_string(), is_external)?; // handles felt arrays stored as string (ex: keys) if let (TypeRef::List(_), Value::String(s)) = (type_ref, &value) { - let mut felts: Vec<_> = s.split(SQL_FELT_DELIMITER).map(Value::from).collect(); + let mut felts: Vec<_> = + s.split(SQL_FELT_DELIMITER).map(Value::from).collect(); felts.pop(); // removes empty item value = Value::List(felts); } @@ -144,18 +145,15 @@ pub fn value_mapping_from_row( TypeData::List(inner) => { let value = fetch_value(row, &column_name, "String", is_external)?; if let Value::String(json_str) = value { - let array_value: Value = serde_json::from_str(&json_str) - .map_err(|e| sqlx::Error::Protocol(format!("JSON parse error: {}", e)))?; + let array_value: Value = serde_json::from_str(&json_str).map_err(|e| { + sqlx::Error::Protocol(format!("JSON parse error: {}", e)) + })?; value_mapping.insert(Name::new(field_name), array_value); } } TypeData::Nested((_, nested_mapping)) => { - let nested_values = build_value_mapping( - row, - nested_mapping, - &column_name, - is_external, - )?; + let nested_values = + build_value_mapping(row, nested_mapping, &column_name, is_external)?; value_mapping.insert(Name::new(field_name), Value::Object(nested_values)); } } @@ -182,11 +180,8 @@ fn fetch_value( type_name: &str, is_external: bool, ) -> sqlx::Result { - let mut column_name = if !is_external { - format!("internal_{}", field_name) - } else { - field_name.to_string() - }; + let mut column_name = + if !is_external { format!("internal_{}", field_name) } else { field_name.to_string() }; // for enum options, remove the ".option" suffix to get the variant // through the enum itself field name From be9c251ec1d9acd8a5578decb668524ce68184ab Mon Sep 17 00:00:00 2001 From: Nasr Date: Fri, 29 Nov 2024 15:43:49 +0700 Subject: [PATCH 21/41] tuple no _ --- crates/torii/core/src/sql/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 7d547d4f7d..d5044acb66 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -873,7 +873,7 @@ impl Sql { Ty::Tuple(tuple) => { for (idx, member) in tuple.iter().enumerate() { let mut new_path = path.to_vec(); - new_path.push(format!("_{}", idx)); + new_path.push(idx.to_string()); self.add_columns_recursive( &new_path, From 47531414d0f854c49e7f6980e4c90fff2193574a Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 13:00:22 +0700 Subject: [PATCH 22/41] feat: graphql arrays of objects and enums --- crates/torii/graphql/src/query/mod.rs | 94 ++++++++++++++++++++++----- crates/torii/graphql/src/types.rs | 7 ++ 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index 41c1407734..5209e06557 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -40,10 +40,13 @@ fn member_to_type_data(namespace: &str, schema: &Ty) -> TypeData { Ty::Array(array) => TypeData::List(Box::new(member_to_type_data(namespace, &array[0]))), // Enums that do not have a nested member are considered as a simple Enum Ty::Enum(enum_) - if enum_ - .options - .iter() - .all(|o| if let Ty::Tuple(t) = &o.ty { t.is_empty() } else { false }) => + if enum_.options.iter().all(|o| { + if let Ty::Tuple(t) = &o.ty { + t.is_empty() + } else { + false + } + }) => { TypeData::Simple(TypeRef::named("Enum")) } @@ -81,6 +84,11 @@ fn parse_nested_type(namespace: &str, schema: &Ty) -> TypeData { type_mapping.insert(Name::new("option"), TypeData::Simple(TypeRef::named("Enum"))); type_mapping } + Ty::Tuple(t) => t + .iter() + .enumerate() + .map(|(i, ty)| (Name::new(&format!("_{}", i)), member_to_type_data(namespace, &ty))) + .collect(), _ => return TypeData::Simple(TypeRef::named(schema.name())), }; @@ -111,12 +119,21 @@ pub fn value_mapping_from_row( types: &TypeMapping, is_external: bool, ) -> sqlx::Result { - println!("types: {:?}", types); + // Retrieve entity ID if present + let entity_id = if let Ok(entity_id) = row.try_get::(ENTITY_ID_COLUMN) { + Some(entity_id) + } else if let Ok(event_message_id) = row.try_get::(EVENT_MESSAGE_ID_COLUMN) { + Some(event_message_id) + } else { + None + }; + fn build_value_mapping( row: &SqliteRow, types: &TypeMapping, prefix: &str, is_external: bool, + entity_id: &Option, ) -> sqlx::Result { let mut value_mapping = ValueMapping::new(); @@ -142,18 +159,67 @@ pub fn value_mapping_from_row( value_mapping.insert(Name::new(field_name), value); } - TypeData::List(inner) => { + TypeData::List(_) => { let value = fetch_value(row, &column_name, "String", is_external)?; if let Value::String(json_str) = value { - let array_value: Value = serde_json::from_str(&json_str).map_err(|e| { - sqlx::Error::Protocol(format!("JSON parse error: {}", e)) - })?; + let mut array_value: Value = + serde_json::from_str(&json_str).map_err(|e| { + sqlx::Error::Protocol(format!("JSON parse error: {}", e)) + })?; + + fn populate_value( + value: &mut Value, + type_data: &TypeData, + entity_id: &Option, + ) { + match value { + Value::Object(obj) => { + for (field_name, field_value) in obj.iter_mut() { + populate_value( + field_value, + &type_data.type_mapping().unwrap()[field_name], + entity_id, + ); + } + + if type_data.type_mapping().map_or(false, |mapping| { + mapping.contains_key(&Name::new("option")) + }) { + obj.insert( + Name::new("option"), + Value::String(obj.keys().next().unwrap().to_string()), + ); + } + + // insert $entity_id$ relation + if let Some(entity_id) = entity_id { + obj.insert( + Name::new(INTERNAL_ENTITY_ID_KEY), + Value::from(entity_id), + ); + } + } + Value::List(inner) => { + for item in inner { + populate_value(item, type_data.inner().unwrap(), entity_id); + } + } + _ => {} + } + } + + populate_value(&mut array_value, &type_data, entity_id); value_mapping.insert(Name::new(field_name), array_value); } } TypeData::Nested((_, nested_mapping)) => { - let nested_values = - build_value_mapping(row, nested_mapping, &column_name, is_external)?; + let nested_values = build_value_mapping( + row, + nested_mapping, + &column_name, + is_external, + entity_id, + )?; value_mapping.insert(Name::new(field_name), Value::Object(nested_values)); } } @@ -162,13 +228,11 @@ pub fn value_mapping_from_row( Ok(value_mapping) } - let mut value_mapping = build_value_mapping(row, types, "", is_external)?; + let mut value_mapping = build_value_mapping(row, types, "", is_external, &entity_id)?; // Add internal entity ID if present - if let Ok(entity_id) = row.try_get::(ENTITY_ID_COLUMN) { + if let Some(entity_id) = entity_id { value_mapping.insert(Name::new(INTERNAL_ENTITY_ID_KEY), Value::from(entity_id)); - } else if let Ok(event_message_id) = row.try_get::(EVENT_MESSAGE_ID_COLUMN) { - value_mapping.insert(Name::new(INTERNAL_ENTITY_ID_KEY), Value::from(event_message_id)); } Ok(value_mapping) diff --git a/crates/torii/graphql/src/types.rs b/crates/torii/graphql/src/types.rs index 14256dd911..11bc5bb378 100644 --- a/crates/torii/graphql/src/types.rs +++ b/crates/torii/graphql/src/types.rs @@ -42,6 +42,13 @@ impl TypeData { matches!(self, TypeData::List(_)) } + pub fn inner(&self) -> Option<&TypeData> { + match self { + TypeData::List(inner) => Some(inner), + _ => None, + } + } + // pub fn is_enum(&self) -> bool { // matches!(self, TypeData::Enum(_)) // } From 39fee00e9490cd63fc6a9b3f53d496005d5f4057 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 13:36:23 +0700 Subject: [PATCH 23/41] feat: tuple support --- crates/torii/graphql/src/query/mod.rs | 50 +++++++++++++++++++++------ 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index 5209e06557..a8e469ab9a 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use async_graphql::dynamic::indexmap::IndexMap; use async_graphql::dynamic::TypeRef; use async_graphql::{Name, Value}; use chrono::{DateTime, Utc}; @@ -136,6 +137,10 @@ pub fn value_mapping_from_row( entity_id: &Option, ) -> sqlx::Result { let mut value_mapping = ValueMapping::new(); + // Add internal entity ID if present + if let Some(entity_id) = entity_id { + value_mapping.insert(Name::new(INTERNAL_ENTITY_ID_KEY), Value::from(entity_id)); + } for (field_name, type_data) in types { let column_name = if prefix.is_empty() { @@ -199,11 +204,26 @@ pub fn value_mapping_from_row( ); } } - Value::List(inner) => { - for item in inner { - populate_value(item, type_data.inner().unwrap(), entity_id); + Value::List(inner) => match type_data { + TypeData::List(inner_type_data) => { + for item in inner.iter_mut() { + populate_value(item, inner_type_data, entity_id); + } } - } + TypeData::Nested((_, mapping)) => { + let mut obj = IndexMap::new(); + for (i, item) in inner.iter_mut().enumerate() { + populate_value( + item, + &mapping[&Name::new(format!("_{}", i))], + entity_id, + ); + obj.insert(Name::new(format!("_{}", i)), item.clone()); + } + *value = Value::Object(obj); + } + _ => {} + }, _ => {} } } @@ -228,13 +248,7 @@ pub fn value_mapping_from_row( Ok(value_mapping) } - let mut value_mapping = build_value_mapping(row, types, "", is_external, &entity_id)?; - - // Add internal entity ID if present - if let Some(entity_id) = entity_id { - value_mapping.insert(Name::new(INTERNAL_ENTITY_ID_KEY), Value::from(entity_id)); - } - + let value_mapping = build_value_mapping(row, types, "", is_external, &entity_id)?; Ok(value_mapping) } @@ -247,6 +261,20 @@ fn fetch_value( let mut column_name = if !is_external { format!("internal_{}", field_name) } else { field_name.to_string() }; + // Strip _0, _1, etc. from tuple field names + // to get the actual SQL column name which is 0, 1 etc.. + column_name = column_name + .split('.') + .map(|part| { + if part.starts_with('_') && part[1..].parse::().is_ok() { + &part[1..] + } else { + part + } + }) + .collect::>() + .join("."); + // for enum options, remove the ".option" suffix to get the variant // through the enum itself field name if type_name == "Enum" && column_name.ends_with(".option") { From 0d4024c0ddffe8c31f0a77ec93827c5eaa94a9a7 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 13:36:54 +0700 Subject: [PATCH 24/41] fmt --- crates/torii/graphql/src/query/mod.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index a8e469ab9a..e187d6256c 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -41,13 +41,10 @@ fn member_to_type_data(namespace: &str, schema: &Ty) -> TypeData { Ty::Array(array) => TypeData::List(Box::new(member_to_type_data(namespace, &array[0]))), // Enums that do not have a nested member are considered as a simple Enum Ty::Enum(enum_) - if enum_.options.iter().all(|o| { - if let Ty::Tuple(t) = &o.ty { - t.is_empty() - } else { - false - } - }) => + if enum_ + .options + .iter() + .all(|o| if let Ty::Tuple(t) = &o.ty { t.is_empty() } else { false }) => { TypeData::Simple(TypeRef::named("Enum")) } From 8c41f2468087bdc9c0d66c7d69de0724a5e00346 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 13:43:01 +0700 Subject: [PATCH 25/41] c --- crates/torii/core/src/model.rs | 44 ++-- crates/torii/core/src/sql/mod.rs | 276 ++++++++++++-------------- crates/torii/graphql/src/query/mod.rs | 4 +- 3 files changed, 155 insertions(+), 169 deletions(-) diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index 411abaebf3..e9a06df904 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -223,23 +223,23 @@ pub fn map_row_to_ty( Ty::Primitive(primitive) => { match &primitive { Primitive::I8(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; primitive.set_i8(Some(value))?; } Primitive::I16(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; primitive.set_i16(Some(value))?; } Primitive::I32(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; primitive.set_i32(Some(value))?; } Primitive::I64(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; primitive.set_i64(Some(value))?; } Primitive::I128(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; let hex_str = value.trim_start_matches("0x"); if !hex_str.is_empty() { @@ -249,19 +249,19 @@ pub fn map_row_to_ty( } } Primitive::U8(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; primitive.set_u8(Some(value))?; } Primitive::U16(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; primitive.set_u16(Some(value))?; } Primitive::U32(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; primitive.set_u32(Some(value))?; } Primitive::U64(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; let hex_str = value.trim_start_matches("0x"); if !hex_str.is_empty() { @@ -271,7 +271,7 @@ pub fn map_row_to_ty( } } Primitive::U128(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; let hex_str = value.trim_start_matches("0x"); if !hex_str.is_empty() { @@ -281,7 +281,7 @@ pub fn map_row_to_ty( } } Primitive::U256(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; let hex_str = value.trim_start_matches("0x"); if !hex_str.is_empty() { @@ -289,15 +289,15 @@ pub fn map_row_to_ty( } } Primitive::USize(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; primitive.set_usize(Some(value))?; } Primitive::Bool(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; primitive.set_bool(Some(value))?; } Primitive::Felt252(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; if !value.is_empty() { primitive.set_felt252(Some( Felt::from_str(&value).map_err(ParseError::FromStr)?, @@ -305,7 +305,7 @@ pub fn map_row_to_ty( } } Primitive::ClassHash(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; if !value.is_empty() { primitive.set_class_hash(Some( Felt::from_str(&value).map_err(ParseError::FromStr)?, @@ -313,7 +313,7 @@ pub fn map_row_to_ty( } } Primitive::ContractAddress(_) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; if !value.is_empty() { primitive.set_contract_address(Some( Felt::from_str(&value).map_err(ParseError::FromStr)?, @@ -323,7 +323,7 @@ pub fn map_row_to_ty( }; } Ty::Enum(enum_ty) => { - let option_name = row.try_get::(&column_name)?; + let option_name = row.try_get::(column_name)?; if !option_name.is_empty() { enum_ty.set_option(&option_name)?; } @@ -333,22 +333,22 @@ pub fn map_row_to_ty( continue; } - map_row_to_ty(&column_name, &option.name, &mut option.ty, row)?; + map_row_to_ty(column_name, &option.name, &mut option.ty, row)?; } } Ty::Struct(struct_ty) => { for member in &mut struct_ty.children { - map_row_to_ty(&column_name, &member.name, &mut member.ty, row)?; + map_row_to_ty(column_name, &member.name, &mut member.ty, row)?; } } Ty::Tuple(ty) => { for (i, member) in ty.iter_mut().enumerate() { - map_row_to_ty(&column_name, &i.to_string(), member, row)?; + map_row_to_ty(column_name, &i.to_string(), member, row)?; } } Ty::Array(ty) => { let schema = ty[0].clone(); - let serialized_array = row.try_get::(&column_name)?; + let serialized_array = row.try_get::(column_name)?; let values: Vec = serde_json::from_str(&serialized_array).map_err(ParseError::FromJsonStr)?; @@ -362,7 +362,7 @@ pub fn map_row_to_ty( .collect::, _>>()?; } Ty::ByteArray(bytearray) => { - let value = row.try_get::(&column_name)?; + let value = row.try_get::(column_name)?; *bytearray = value; } }; diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index d5044acb66..8f2b90bd37 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -285,10 +285,8 @@ impl Sql { ))?; self.build_model_query( - selector, vec![namespaced_name.clone()], model, - block_timestamp, upgrade_diff, )?; @@ -748,10 +746,8 @@ impl Sql { #[allow(clippy::too_many_arguments)] fn build_model_query( &mut self, - selector: Felt, path: Vec, model: &Ty, - block_timestamp: u64, upgrade_diff: Option<&Ty>, ) -> Result<()> { let table_id = path[0].clone(); // Use only the root path component @@ -767,15 +763,13 @@ impl Sql { ); // Recursively add columns for all nested type - self.add_columns_recursive( + add_columns_recursive( &path, model, &mut columns, &mut alter_table_queries, &mut indices, &table_id, - selector, - block_timestamp, upgrade_diff, )?; @@ -813,160 +807,152 @@ impl Sql { Ok(()) } - fn add_columns_recursive( - &mut self, - path: &[String], - ty: &Ty, - columns: &mut Vec, - alter_table_queries: &mut Vec, - indices: &mut Vec, - table_id: &str, - selector: Felt, - block_timestamp: u64, - upgrade_diff: Option<&Ty>, - ) -> Result<()> { - let column_prefix = if path.len() > 1 { path[1..].join(".") } else { String::new() }; + + pub async fn execute(&self) -> Result<()> { + let (execute, recv) = QueryMessage::execute_recv(); + self.executor.send(execute)?; + recv.await? + } - let mut add_column = |name: &str, sql_type: &str| { - if upgrade_diff.is_some() { - alter_table_queries - .push(format!("ALTER TABLE [{table_id}] ADD COLUMN [{name}] {sql_type}")); - } else { - columns.push(format!("[{name}] {sql_type}")); - } - indices.push(format!( - "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{name}] ON [{table_id}] ([{name}]);" - )); - }; + pub async fn flush(&self) -> Result<()> { + let (flush, recv) = QueryMessage::flush_recv(); + self.executor.send(flush)?; + recv.await? + } - match ty { - Ty::Struct(s) => { - for member in &s.children { - if let Some(upgrade_diff) = upgrade_diff { - if !upgrade_diff - .as_struct() - .unwrap() - .children - .iter() - .any(|m| m.name == member.name) - { - continue; - } - } + pub async fn rollback(&self) -> Result<()> { + let (rollback, recv) = QueryMessage::rollback_recv(); + self.executor.send(rollback)?; + recv.await? + } +} - let mut new_path = path.to_vec(); - new_path.push(member.name.clone()); - - self.add_columns_recursive( - &new_path, - &member.ty, - columns, - alter_table_queries, - indices, - table_id, - selector, - block_timestamp, - None, - )?; +fn add_columns_recursive( + path: &[String], + ty: &Ty, + columns: &mut Vec, + alter_table_queries: &mut Vec, + indices: &mut Vec, + table_id: &str, + upgrade_diff: Option<&Ty>, +) -> Result<()> { + let column_prefix = if path.len() > 1 { path[1..].join(".") } else { String::new() }; + + let mut add_column = |name: &str, sql_type: &str| { + if upgrade_diff.is_some() { + alter_table_queries + .push(format!("ALTER TABLE [{table_id}] ADD COLUMN [{name}] {sql_type}")); + } else { + columns.push(format!("[{name}] {sql_type}")); + } + indices.push(format!( + "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{name}] ON [{table_id}] ([{name}]);" + )); + }; + + match ty { + Ty::Struct(s) => { + for member in &s.children { + if let Some(upgrade_diff) = upgrade_diff { + if !upgrade_diff + .as_struct() + .unwrap() + .children + .iter() + .any(|m| m.name == member.name) + { + continue; + } } + + let mut new_path = path.to_vec(); + new_path.push(member.name.clone()); + + add_columns_recursive( + &new_path, + &member.ty, + columns, + alter_table_queries, + indices, + table_id, + None, + )?; } - Ty::Tuple(tuple) => { - for (idx, member) in tuple.iter().enumerate() { - let mut new_path = path.to_vec(); - new_path.push(idx.to_string()); - - self.add_columns_recursive( - &new_path, - member, - columns, - alter_table_queries, - indices, - table_id, - selector, - block_timestamp, - None, - )?; - } + } + Ty::Tuple(tuple) => { + for (idx, member) in tuple.iter().enumerate() { + let mut new_path = path.to_vec(); + new_path.push(idx.to_string()); + + add_columns_recursive( + &new_path, + member, + columns, + alter_table_queries, + indices, + table_id, + None, + )?; } - Ty::Array(_) => { - let column_name = - if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; + } + Ty::Array(_) => { + let column_name = + if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; - add_column(&column_name, "TEXT"); - } - Ty::Enum(e) => { - // The variant of the enum - let column_name = if column_prefix.is_empty() { - "option".to_string() - } else { - format!("{}", column_prefix) - }; - - let all_options = e - .options - .iter() - .map(|c| format!("'{}'", c.name)) - .collect::>() - .join(", "); + add_column(&column_name, "TEXT"); + } + Ty::Enum(e) => { + // The variant of the enum + let column_name = if column_prefix.is_empty() { + "option".to_string() + } else { + column_prefix + }; - let sql_type = format!("TEXT CHECK({} IN ({}))", column_name, all_options); - add_column(&column_name, &sql_type); + let all_options = e + .options + .iter() + .map(|c| format!("'{}'", c.name)) + .collect::>() + .join(", "); - for child in &e.options { - if let Ty::Tuple(tuple) = &child.ty { - if tuple.is_empty() { - continue; - } - } + let sql_type = format!("TEXT CHECK({} IN ({}))", column_name, all_options); + add_column(&column_name, &sql_type); - let mut new_path = path.to_vec(); - new_path.push(child.name.clone()); - - self.add_columns_recursive( - &new_path, - &child.ty, - columns, - alter_table_queries, - indices, - table_id, - selector, - block_timestamp, - None, - )?; + for child in &e.options { + if let Ty::Tuple(tuple) = &child.ty { + if tuple.is_empty() { + continue; + } } - } - Ty::ByteArray(_) => { - let column_name = - if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; - - add_column(&column_name, "TEXT"); - } - Ty::Primitive(p) => { - let column_name = - if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; - add_column(&column_name, &p.to_sql_type().to_string()); + let mut new_path = path.to_vec(); + new_path.push(child.name.clone()); + + add_columns_recursive( + &new_path, + &child.ty, + columns, + alter_table_queries, + indices, + table_id, + None, + )?; } } + Ty::ByteArray(_) => { + let column_name = + if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; - Ok(()) - } - - pub async fn execute(&self) -> Result<()> { - let (execute, recv) = QueryMessage::execute_recv(); - self.executor.send(execute)?; - recv.await? - } + add_column(&column_name, "TEXT"); + } + Ty::Primitive(p) => { + let column_name = + if column_prefix.is_empty() { "value".to_string() } else { column_prefix }; - pub async fn flush(&self) -> Result<()> { - let (flush, recv) = QueryMessage::flush_recv(); - self.executor.send(flush)?; - recv.await? + add_column(&column_name, p.to_sql_type().as_ref()); + } } - pub async fn rollback(&self) -> Result<()> { - let (rollback, recv) = QueryMessage::rollback_recv(); - self.executor.send(rollback)?; - recv.await? - } + Ok(()) } diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index e187d6256c..b4e92847c0 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -85,7 +85,7 @@ fn parse_nested_type(namespace: &str, schema: &Ty) -> TypeData { Ty::Tuple(t) => t .iter() .enumerate() - .map(|(i, ty)| (Name::new(&format!("_{}", i)), member_to_type_data(namespace, &ty))) + .map(|(i, ty)| (Name::new(format!("_{}", i)), member_to_type_data(namespace, ty))) .collect(), _ => return TypeData::Simple(TypeRef::named(schema.name())), }; @@ -225,7 +225,7 @@ pub fn value_mapping_from_row( } } - populate_value(&mut array_value, &type_data, entity_id); + populate_value(&mut array_value, type_data, entity_id); value_mapping.insert(Name::new(field_name), array_value); } } From 7a78ae680c58f672f3f4d2e16b563e778500d06f Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 13:43:12 +0700 Subject: [PATCH 26/41] f --- crates/torii/core/src/sql/mod.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 8f2b90bd37..f5e6dde9a4 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -284,11 +284,7 @@ impl Sql { QueryType::RegisterModel, ))?; - self.build_model_query( - vec![namespaced_name.clone()], - model, - upgrade_diff, - )?; + self.build_model_query(vec![namespaced_name.clone()], model, upgrade_diff)?; // we set the model in the cache directly // because entities might be using it before the query queue is processed @@ -807,7 +803,6 @@ impl Sql { Ok(()) } - pub async fn execute(&self) -> Result<()> { let (execute, recv) = QueryMessage::execute_recv(); self.executor.send(execute)?; @@ -903,18 +898,11 @@ fn add_columns_recursive( } Ty::Enum(e) => { // The variant of the enum - let column_name = if column_prefix.is_empty() { - "option".to_string() - } else { - column_prefix - }; + let column_name = + if column_prefix.is_empty() { "option".to_string() } else { column_prefix }; - let all_options = e - .options - .iter() - .map(|c| format!("'{}'", c.name)) - .collect::>() - .join(", "); + let all_options = + e.options.iter().map(|c| format!("'{}'", c.name)).collect::>().join(", "); let sql_type = format!("TEXT CHECK({} IN ({}))", column_name, all_options); add_column(&column_name, &sql_type); From ed8f5efd9766b4c30cdcbe8687d723f8df77b5e0 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 13:45:15 +0700 Subject: [PATCH 27/41] fix --- crates/torii/core/src/model.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index e9a06df904..3c4bd315ee 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -490,9 +490,17 @@ mod tests { ) .unwrap(); - let expected_query = "SELECT internal_id, internal_entity_id, [player], [vec.x], [vec.y], \ - [test_everything], [favorite_item], [favorite_item.Some], [items] \ - FROM [entities] WHERE entity_id ORDER BY internal_event_id DESC"; + let expected_query = + "SELECT entities.id, entities.keys, [Test-Position].[player] as \ + \"Test-Position.player\", [Test-Position].[vec.x] as \"Test-Position.vec.x\", \ + [Test-Position].[vec.y] as \"Test-Position.vec.y\", \ + [Test-Position].[test_everything] as \"Test-Position.test_everything\", \ + [Test-PlayerConfig].[favorite_item] as \"Test-PlayerConfig.favorite_item\", \ + [Test-PlayerConfig].[favorite_item.Some] as \ + \"Test-PlayerConfig.favorite_item.Some\", [Test-PlayerConfig].[items] as \ + \"Test-PlayerConfig.items\" FROM [entities] LEFT JOIN [Test-Position] ON entities.id \ + = [Test-Position].internal_entity_id LEFT JOIN [Test-PlayerConfig] ON entities.id = \ + [Test-PlayerConfig].internal_entity_id ORDER BY entities.event_id DESC"; assert_eq!(query.0, expected_query); } } From 9b93981157cddd23bb027f1bc53aeb0fe3db83d8 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 16:05:11 +0700 Subject: [PATCH 28/41] fix entities --- crates/torii/graphql/src/query/mod.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index b4e92847c0..6bb16d806a 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -4,6 +4,7 @@ use async_graphql::dynamic::indexmap::IndexMap; use async_graphql::dynamic::TypeRef; use async_graphql::{Name, Value}; use chrono::{DateTime, Utc}; +use convert_case::{Case, Casing}; use dojo_types::primitive::{Primitive, SqlType}; use dojo_types::schema::Ty; use regex::Regex; @@ -260,17 +261,21 @@ fn fetch_value( // Strip _0, _1, etc. from tuple field names // to get the actual SQL column name which is 0, 1 etc.. - column_name = column_name - .split('.') - .map(|part| { - if part.starts_with('_') && part[1..].parse::().is_ok() { - &part[1..] - } else { - part - } - }) - .collect::>() - .join("."); + column_name = if column_name.contains('.') { + column_name + .split('.') + .map(|part| { + if part.starts_with('_') && part[1..].parse::().is_ok() { + &part[1..] + } else { + part + } + }) + .collect::>() + .join(".") + } else { + column_name.to_case(Case::Snake) + }; // for enum options, remove the ".option" suffix to get the variant // through the enum itself field name From d295c0fb4751e94cb397f1ce38af4e014930b1bf Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 16:25:01 +0700 Subject: [PATCH 29/41] refactor: value mapping fix --- .../torii/graphql/src/object/connection/mod.rs | 6 +++--- crates/torii/graphql/src/object/entity.rs | 2 +- crates/torii/graphql/src/object/event_message.rs | 2 +- crates/torii/graphql/src/object/mod.rs | 4 ++-- crates/torii/graphql/src/object/model_data.rs | 4 ++-- crates/torii/graphql/src/query/mod.rs | 16 ++++++++-------- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/torii/graphql/src/object/connection/mod.rs b/crates/torii/graphql/src/object/connection/mod.rs index d979106333..1c8bb21fc7 100644 --- a/crates/torii/graphql/src/object/connection/mod.rs +++ b/crates/torii/graphql/src/object/connection/mod.rs @@ -117,7 +117,7 @@ pub fn connection_output( order: &Option, id_column: &str, total_count: i64, - is_external: bool, + is_internal: bool, page_info: PageInfo, ) -> sqlx::Result { let model_edges = data @@ -125,7 +125,7 @@ pub fn connection_output( .map(|row| { let order_field = match order { Some(order) => { - if !is_external { + if is_internal { format!("internal_{}", order.field) } else { order.field.to_string() @@ -136,7 +136,7 @@ pub fn connection_output( let primary_order = row.try_get::(id_column)?; let secondary_order = row.try_get_unchecked::(&order_field)?; let cursor = cursor::encode(&primary_order, &secondary_order); - let value_mapping = value_mapping_from_row(row, types, is_external)?; + let value_mapping = value_mapping_from_row(row, types, is_internal)?; let mut edge = ValueMapping::new(); edge.insert(Name::new("node"), Value::Object(value_mapping)); diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index 3882413380..b18bc1a58c 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -150,7 +150,7 @@ fn model_union_field() -> Field { sqlx::query(&query).bind(&entity_id).fetch_one(&mut *conn).await?; // Use value_mapping_from_row to handle nested structures - let data = value_mapping_from_row(&row, &type_mapping, true)?; + let data = value_mapping_from_row(&row, &type_mapping, false)?; results.push(FieldValue::with_type( FieldValue::owned_any(data), diff --git a/crates/torii/graphql/src/object/event_message.rs b/crates/torii/graphql/src/object/event_message.rs index c704e9ad5a..d26c733dfc 100644 --- a/crates/torii/graphql/src/object/event_message.rs +++ b/crates/torii/graphql/src/object/event_message.rs @@ -158,7 +158,7 @@ fn model_union_field() -> Field { sqlx::query(&query).bind(&entity_id).fetch_one(&mut *conn).await?; // Use value_mapping_from_row to handle nested structures - let data = value_mapping_from_row(&row, &type_mapping, true)?; + let data = value_mapping_from_row(&row, &type_mapping, false)?; results.push(FieldValue::with_type( FieldValue::owned_any(data), diff --git a/crates/torii/graphql/src/object/mod.rs b/crates/torii/graphql/src/object/mod.rs index b2a8d4063e..b4201e0989 100644 --- a/crates/torii/graphql/src/object/mod.rs +++ b/crates/torii/graphql/src/object/mod.rs @@ -261,7 +261,7 @@ pub fn resolve_one( let id: String = extract::(ctx.args.as_index_map(), &id_column.to_case(Case::Camel))?; let data = fetch_single_row(&mut conn, &table_name, &id_column, &id).await?; - let model = value_mapping_from_row(&data, &type_mapping, true)?; + let model = value_mapping_from_row(&data, &type_mapping, false)?; Ok(Some(Value::Object(model))) }) }) @@ -310,7 +310,7 @@ pub fn resolve_many( &order, &id_column, total_count, - true, + false, page_info, )?; diff --git a/crates/torii/graphql/src/object/model_data.rs b/crates/torii/graphql/src/object/model_data.rs index 5afd6b0dac..bf7230a33a 100644 --- a/crates/torii/graphql/src/object/model_data.rs +++ b/crates/torii/graphql/src/object/model_data.rs @@ -119,7 +119,7 @@ impl ResolvableObject for ModelDataObject { &order, "internal_event_id", total_count, - true, + false, page_info, )?; @@ -200,7 +200,7 @@ pub fn object(type_name: &str, type_mapping: &TypeMapping, path_array: Vec Value { pub fn value_mapping_from_row( row: &SqliteRow, types: &TypeMapping, - is_external: bool, + is_internal: bool, ) -> sqlx::Result { // Retrieve entity ID if present let entity_id = if let Ok(entity_id) = row.try_get::(ENTITY_ID_COLUMN) { @@ -131,7 +131,7 @@ pub fn value_mapping_from_row( row: &SqliteRow, types: &TypeMapping, prefix: &str, - is_external: bool, + is_internal: bool, entity_id: &Option, ) -> sqlx::Result { let mut value_mapping = ValueMapping::new(); @@ -150,7 +150,7 @@ pub fn value_mapping_from_row( match type_data { TypeData::Simple(type_ref) => { let mut value = - fetch_value(row, &column_name, &type_ref.to_string(), is_external)?; + fetch_value(row, &column_name, &type_ref.to_string(), is_internal)?; // handles felt arrays stored as string (ex: keys) if let (TypeRef::List(_), Value::String(s)) = (type_ref, &value) { @@ -163,7 +163,7 @@ pub fn value_mapping_from_row( value_mapping.insert(Name::new(field_name), value); } TypeData::List(_) => { - let value = fetch_value(row, &column_name, "String", is_external)?; + let value = fetch_value(row, &column_name, "String", is_internal)?; if let Value::String(json_str) = value { let mut array_value: Value = serde_json::from_str(&json_str).map_err(|e| { @@ -235,7 +235,7 @@ pub fn value_mapping_from_row( row, nested_mapping, &column_name, - is_external, + is_internal, entity_id, )?; value_mapping.insert(Name::new(field_name), Value::Object(nested_values)); @@ -246,7 +246,7 @@ pub fn value_mapping_from_row( Ok(value_mapping) } - let value_mapping = build_value_mapping(row, types, "", is_external, &entity_id)?; + let value_mapping = build_value_mapping(row, types, "", is_internal, &entity_id)?; Ok(value_mapping) } @@ -254,10 +254,10 @@ fn fetch_value( row: &SqliteRow, field_name: &str, type_name: &str, - is_external: bool, + is_internal: bool, ) -> sqlx::Result { let mut column_name = - if !is_external { format!("internal_{}", field_name) } else { field_name.to_string() }; + if is_internal { format!("internal_{}", field_name) } else { field_name.to_string() }; // Strip _0, _1, etc. from tuple field names // to get the actual SQL column name which is 0, 1 etc.. From 7bb0853762c46fde5a25b6d6ebbc9809cb0902e8 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 16:48:07 +0700 Subject: [PATCH 30/41] types --- crates/torii/graphql/src/query/mod.rs | 31 ++++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index 95e4f604b5..2acb39cd45 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -261,21 +261,22 @@ fn fetch_value( // Strip _0, _1, etc. from tuple field names // to get the actual SQL column name which is 0, 1 etc.. - column_name = if column_name.contains('.') { - column_name - .split('.') - .map(|part| { - if part.starts_with('_') && part[1..].parse::().is_ok() { - &part[1..] - } else { - part - } - }) - .collect::>() - .join(".") - } else { - column_name.to_case(Case::Snake) - }; + column_name = column_name + .split('.') + .map(|part| { + if part.starts_with('_') && part[1..].parse::().is_ok() { + &part[1..] + } else { + part + } + }) + .collect::>() + .join("."); + + // we do this for fields like eventId, classHash etc. which are stored as snake_case in the DB + if !column_name.contains('_') { + column_name = column_name.to_case(Case::Snake); + } // for enum options, remove the ".option" suffix to get the variant // through the enum itself field name From f9255686b8eb5322b63c1ce1dff578584cb39769 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 17:16:31 +0700 Subject: [PATCH 31/41] fix: snake case --- crates/torii/graphql/src/query/mod.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index 2acb39cd45..78e7cd6c48 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -261,23 +261,23 @@ fn fetch_value( // Strip _0, _1, etc. from tuple field names // to get the actual SQL column name which is 0, 1 etc.. - column_name = column_name - .split('.') - .map(|part| { + let parts: Vec<_> = column_name.split('.').collect(); + + column_name = parts + .iter() + .enumerate() + .map(|(i, part)| { if part.starts_with('_') && part[1..].parse::().is_ok() { - &part[1..] + part[1..].to_string() + } else if i == parts.len() - 1 { + part.to_case(Case::Snake) } else { - part + part.to_string() } }) .collect::>() .join("."); - // we do this for fields like eventId, classHash etc. which are stored as snake_case in the DB - if !column_name.contains('_') { - column_name = column_name.to_case(Case::Snake); - } - // for enum options, remove the ".option" suffix to get the variant // through the enum itself field name if type_name == "Enum" && column_name.ends_with(".option") { From 530e14b18eb3f8413d05ee75d2aa4aeeb9de31ee Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 17:49:07 +0700 Subject: [PATCH 32/41] fix: casing --- .../graphql/src/object/connection/mod.rs | 3 +- crates/torii/graphql/src/object/entity.rs | 2 +- .../torii/graphql/src/object/event_message.rs | 2 +- .../torii/graphql/src/object/metadata/mod.rs | 2 +- crates/torii/graphql/src/object/mod.rs | 3 +- crates/torii/graphql/src/object/model_data.rs | 9 +++-- crates/torii/graphql/src/query/mod.rs | 35 +++++++++++-------- 7 files changed, 34 insertions(+), 22 deletions(-) diff --git a/crates/torii/graphql/src/object/connection/mod.rs b/crates/torii/graphql/src/object/connection/mod.rs index 1c8bb21fc7..1ed560cee3 100644 --- a/crates/torii/graphql/src/object/connection/mod.rs +++ b/crates/torii/graphql/src/object/connection/mod.rs @@ -118,6 +118,7 @@ pub fn connection_output( id_column: &str, total_count: i64, is_internal: bool, + snake_case: bool, page_info: PageInfo, ) -> sqlx::Result { let model_edges = data @@ -136,7 +137,7 @@ pub fn connection_output( let primary_order = row.try_get::(id_column)?; let secondary_order = row.try_get_unchecked::(&order_field)?; let cursor = cursor::encode(&primary_order, &secondary_order); - let value_mapping = value_mapping_from_row(row, types, is_internal)?; + let value_mapping = value_mapping_from_row(row, types, is_internal, snake_case)?; let mut edge = ValueMapping::new(); edge.insert(Name::new("node"), Value::Object(value_mapping)); diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index b18bc1a58c..b99935f29c 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -150,7 +150,7 @@ fn model_union_field() -> Field { sqlx::query(&query).bind(&entity_id).fetch_one(&mut *conn).await?; // Use value_mapping_from_row to handle nested structures - let data = value_mapping_from_row(&row, &type_mapping, false)?; + let data = value_mapping_from_row(&row, &type_mapping, false, false)?; results.push(FieldValue::with_type( FieldValue::owned_any(data), diff --git a/crates/torii/graphql/src/object/event_message.rs b/crates/torii/graphql/src/object/event_message.rs index d26c733dfc..155db80f95 100644 --- a/crates/torii/graphql/src/object/event_message.rs +++ b/crates/torii/graphql/src/object/event_message.rs @@ -158,7 +158,7 @@ fn model_union_field() -> Field { sqlx::query(&query).bind(&entity_id).fetch_one(&mut *conn).await?; // Use value_mapping_from_row to handle nested structures - let data = value_mapping_from_row(&row, &type_mapping, false)?; + let data = value_mapping_from_row(&row, &type_mapping, false, false)?; results.push(FieldValue::with_type( FieldValue::owned_any(data), diff --git a/crates/torii/graphql/src/object/metadata/mod.rs b/crates/torii/graphql/src/object/metadata/mod.rs index 07a7771380..8f5efca950 100644 --- a/crates/torii/graphql/src/object/metadata/mod.rs +++ b/crates/torii/graphql/src/object/metadata/mod.rs @@ -105,7 +105,7 @@ fn metadata_connection_output( .map(|row| { let order = row.try_get::(ID_COLUMN)?; let cursor = cursor::encode(&order, &order); - let mut value_mapping = value_mapping_from_row(row, row_types, false)?; + let mut value_mapping = value_mapping_from_row(row, row_types, false, true)?; value_mapping.insert(Name::new("worldAddress"), Value::from(world_address)); let json_str = row.try_get::(JSON_COLUMN)?; diff --git a/crates/torii/graphql/src/object/mod.rs b/crates/torii/graphql/src/object/mod.rs index b4201e0989..5791d606e2 100644 --- a/crates/torii/graphql/src/object/mod.rs +++ b/crates/torii/graphql/src/object/mod.rs @@ -261,7 +261,7 @@ pub fn resolve_one( let id: String = extract::(ctx.args.as_index_map(), &id_column.to_case(Case::Camel))?; let data = fetch_single_row(&mut conn, &table_name, &id_column, &id).await?; - let model = value_mapping_from_row(&data, &type_mapping, false)?; + let model = value_mapping_from_row(&data, &type_mapping, false, true)?; Ok(Some(Value::Object(model))) }) }) @@ -311,6 +311,7 @@ pub fn resolve_many( &id_column, total_count, false, + true, page_info, )?; diff --git a/crates/torii/graphql/src/object/model_data.rs b/crates/torii/graphql/src/object/model_data.rs index bf7230a33a..8f6f0d0bc7 100644 --- a/crates/torii/graphql/src/object/model_data.rs +++ b/crates/torii/graphql/src/object/model_data.rs @@ -120,6 +120,7 @@ impl ResolvableObject for ModelDataObject { "internal_event_id", total_count, false, + false, page_info, )?; @@ -200,7 +201,8 @@ pub fn object(type_name: &str, type_mapping: &TypeMapping, path_array: Vec Field { let entity_id = utils::extract::(indexmap, INTERNAL_ENTITY_ID_KEY)?; let data = fetch_single_row(&mut conn, ENTITY_TABLE, ID_COLUMN, &entity_id).await?; - let entity = value_mapping_from_row(&data, &ENTITY_TYPE_MAPPING, false)?; + let entity = value_mapping_from_row(&data, &ENTITY_TYPE_MAPPING, false, true)?; Ok(Some(Value::Object(entity))) } @@ -262,7 +264,8 @@ fn event_message_field() -> Field { let data = fetch_single_row(&mut conn, EVENT_MESSAGE_TABLE, ID_COLUMN, &entity_id) .await?; - let event_message = value_mapping_from_row(&data, &ENTITY_TYPE_MAPPING, false)?; + let event_message = + value_mapping_from_row(&data, &ENTITY_TYPE_MAPPING, false, true)?; Ok(Some(Value::Object(event_message))) } diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index 78e7cd6c48..94082e3546 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -117,6 +117,7 @@ pub fn value_mapping_from_row( row: &SqliteRow, types: &TypeMapping, is_internal: bool, + snake_case: bool, ) -> sqlx::Result { // Retrieve entity ID if present let entity_id = if let Ok(entity_id) = row.try_get::(ENTITY_ID_COLUMN) { @@ -132,6 +133,7 @@ pub fn value_mapping_from_row( types: &TypeMapping, prefix: &str, is_internal: bool, + snake_case: bool, entity_id: &Option, ) -> sqlx::Result { let mut value_mapping = ValueMapping::new(); @@ -149,8 +151,13 @@ pub fn value_mapping_from_row( match type_data { TypeData::Simple(type_ref) => { - let mut value = - fetch_value(row, &column_name, &type_ref.to_string(), is_internal)?; + let mut value = fetch_value( + row, + &column_name, + &type_ref.to_string(), + is_internal, + snake_case, + )?; // handles felt arrays stored as string (ex: keys) if let (TypeRef::List(_), Value::String(s)) = (type_ref, &value) { @@ -163,7 +170,7 @@ pub fn value_mapping_from_row( value_mapping.insert(Name::new(field_name), value); } TypeData::List(_) => { - let value = fetch_value(row, &column_name, "String", is_internal)?; + let value = fetch_value(row, &column_name, "String", is_internal, snake_case)?; if let Value::String(json_str) = value { let mut array_value: Value = serde_json::from_str(&json_str).map_err(|e| { @@ -236,6 +243,7 @@ pub fn value_mapping_from_row( nested_mapping, &column_name, is_internal, + snake_case, entity_id, )?; value_mapping.insert(Name::new(field_name), Value::Object(nested_values)); @@ -246,7 +254,7 @@ pub fn value_mapping_from_row( Ok(value_mapping) } - let value_mapping = build_value_mapping(row, types, "", is_internal, &entity_id)?; + let value_mapping = build_value_mapping(row, types, "", is_internal, snake_case, &entity_id)?; Ok(value_mapping) } @@ -255,22 +263,21 @@ fn fetch_value( field_name: &str, type_name: &str, is_internal: bool, + snake_case: bool, ) -> sqlx::Result { - let mut column_name = - if is_internal { format!("internal_{}", field_name) } else { field_name.to_string() }; + let mut column_name = if is_internal { + format!("internal_{}", field_name) + } else { + if snake_case { field_name.to_case(Case::Snake) } else { field_name.to_string() } + }; // Strip _0, _1, etc. from tuple field names // to get the actual SQL column name which is 0, 1 etc.. - let parts: Vec<_> = column_name.split('.').collect(); - - column_name = parts - .iter() - .enumerate() - .map(|(i, part)| { + column_name = column_name + .split('.') + .map(|part| { if part.starts_with('_') && part[1..].parse::().is_ok() { part[1..].to_string() - } else if i == parts.len() - 1 { - part.to_case(Case::Snake) } else { part.to_string() } From 49e6d01930ad376ac061d6dac1cb89fd005d8d1b Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 17:57:45 +0700 Subject: [PATCH 33/41] f --- crates/torii/graphql/src/object/connection/mod.rs | 1 + crates/torii/graphql/src/query/mod.rs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/torii/graphql/src/object/connection/mod.rs b/crates/torii/graphql/src/object/connection/mod.rs index 1ed560cee3..540a59c574 100644 --- a/crates/torii/graphql/src/object/connection/mod.rs +++ b/crates/torii/graphql/src/object/connection/mod.rs @@ -111,6 +111,7 @@ pub fn connection_arguments(field: Field) -> Field { .argument(InputValue::new("limit", TypeRef::named(TypeRef::INT))) } +#[allow(clippy::too_many_arguments)] pub fn connection_output( data: &[SqliteRow], types: &TypeMapping, diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index 94082e3546..69c8d4113b 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -267,8 +267,10 @@ fn fetch_value( ) -> sqlx::Result { let mut column_name = if is_internal { format!("internal_{}", field_name) + } else if snake_case { + field_name.to_case(Case::Snake) } else { - if snake_case { field_name.to_case(Case::Snake) } else { field_name.to_string() } + field_name.to_string() }; // Strip _0, _1, etc. from tuple field names From 34d0b72a67fa901f4cd8e4dd9e8fefff0042808d Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 18:06:11 +0700 Subject: [PATCH 34/41] namespaced schema --- crates/torii/core/src/sql/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index f5e6dde9a4..7a40207e49 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -258,6 +258,10 @@ impl Sql { ) -> Result<()> { let selector = compute_selector_from_names(namespace, &model.name()); let namespaced_name = get_tag(namespace, &model.name()); + let namespaced_schema = Ty::Struct(Struct { + name: namespaced_name.clone(), + children: model.as_struct().unwrap().children.clone(), + }); let insert_models = "INSERT INTO models (id, namespace, name, class_hash, contract_address, layout, \ @@ -273,7 +277,7 @@ impl Sql { Argument::String(format!("{class_hash:#x}")), Argument::String(format!("{contract_address:#x}")), Argument::String(serde_json::to_string(&layout)?), - Argument::String(serde_json::to_string(&model)?), + Argument::String(serde_json::to_string(&namespaced_schema)?), Argument::Int(packed_size as i64), Argument::Int(unpacked_size as i64), Argument::String(utc_dt_string_from_timestamp(block_timestamp)), @@ -300,11 +304,7 @@ impl Sql { packed_size, unpacked_size, layout, - // we need to update the name of the struct to include the namespace - schema: Ty::Struct(Struct { - name: namespaced_name, - children: model.as_struct().unwrap().children.clone(), - }), + schema: namespaced_schema, }, ) .await; From 2f1d7ef81e87a6d97de65c4a0c92b5142463136b Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 18:30:32 +0700 Subject: [PATCH 35/41] fix grpc test --- crates/torii/grpc/src/server/mod.rs | 2 -- crates/torii/grpc/src/server/tests/entities_test.rs | 2 +- examples/spawn-and-move/Scarb.lock | 2 +- examples/spawn-and-move/manifest_dev.json | 6 +++--- examples/spawn-and-move/src/actions.cairo | 13 +++++++++---- examples/spawn-and-move/src/models.cairo | 3 +++ 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index 00c52a3d38..39a859ee1d 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -310,8 +310,6 @@ impl DojoWorld { None, )?; - println!("entity_query: {}", entity_query); - let rows = sqlx::query(&entity_query).bind(&models_str).fetch_all(&mut *tx).await?; let schemas = Arc::new(schemas); diff --git a/crates/torii/grpc/src/server/tests/entities_test.rs b/crates/torii/grpc/src/server/tests/entities_test.rs index 031779da58..47c08e2593 100644 --- a/crates/torii/grpc/src/server/tests/entities_test.rs +++ b/crates/torii/grpc/src/server/tests/entities_test.rs @@ -132,7 +132,7 @@ async fn test_entities_queries(sequencer: &RunnerCtx) { .query_by_keys( "entities", "entity_model", - "entity_id", + "internal_entity_id", &KeysClause { keys: vec![account.address().to_bytes_be().to_vec()], pattern_matching: 0, diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 1d6835aa49..0537ff21a2 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -31,7 +31,7 @@ dependencies = [ [[package]] name = "dojo_examples" -version = "1.0.2" +version = "1.0.3" dependencies = [ "armory", "bestiary", diff --git a/examples/spawn-and-move/manifest_dev.json b/examples/spawn-and-move/manifest_dev.json index 057ee7d64d..291337ef74 100644 --- a/examples/spawn-and-move/manifest_dev.json +++ b/examples/spawn-and-move/manifest_dev.json @@ -1252,8 +1252,8 @@ }, "contracts": [ { - "address": "0x7e8a52c68b243d3a86a55c04ccec2edc760252d5952566d3af4001bd6cf38f3", - "class_hash": "0x636c2cf31b094097625cb5ada96f54ee9a3f7bc6d8cde00cc85e5ef0c622c8b", + "address": "0x2e0a8cf3c5d1bc2a4f3ad4e401b50c9b19c490e5b33bfd333d73b3c72669b7f", + "class_hash": "0x40aa706a235ab9f4ce4c415e140856e737834de42978cecd96bf2f647ff2957", "abi": [ { "type": "impl", @@ -2167,7 +2167,7 @@ }, { "members": [], - "class_hash": "0x77018639b9a36824fe474c7c2b2ba7b9ef0f30fa54be1d1f62037dbfc01a89b", + "class_hash": "0x78a98614cad448d6a653492736190834e0318f7330e67c648a8d06ebb387460", "tag": "ns-PlayerConfig", "selector": "0x3bea561c3e142a660a00d1471d7712b70695dc4ee3b173aeaefd5178f7a21af" }, diff --git a/examples/spawn-and-move/src/actions.cairo b/examples/spawn-and-move/src/actions.cairo index 1a8071ada3..9654eb6b2a 100644 --- a/examples/spawn-and-move/src/actions.cairo +++ b/examples/spawn-and-move/src/actions.cairo @@ -64,7 +64,9 @@ pub mod actions { player: seed.try_into().unwrap(), name: "hello", items: array![], - favorite_item: Option::None + favorite_item: Option::None, + test: array![], + test_tuple: (1, 2, PlayerItem { item_id: 1, quantity: 100, score: 150, test: (1, 2) }) }; let mut world = self.world_default(); @@ -123,11 +125,14 @@ pub mod actions { let player = get_caller_address(); let items = array![ - PlayerItem { item_id: 1, quantity: 100, score: 150 }, - PlayerItem { item_id: 2, quantity: 50, score: -32 } + PlayerItem { item_id: 1, quantity: 100, score: 150, test: (1, 2) }, + PlayerItem { item_id: 2, quantity: 50, score: -32, test: (3, 4) } ]; - let config = PlayerConfig { player, name, items, favorite_item: Option::Some(1), }; + let item = items[0].clone(); + let config = PlayerConfig { player, name, items, favorite_item: Option::Some(1), test: array![ + array![Option::Some(item)], + ], test_tuple: (1, 2, item) }; world.write_model(@config); } diff --git a/examples/spawn-and-move/src/models.cairo b/examples/spawn-and-move/src/models.cairo index fc389d2cc9..bb7ea288e4 100644 --- a/examples/spawn-and-move/src/models.cairo +++ b/examples/spawn-and-move/src/models.cairo @@ -75,6 +75,7 @@ pub struct PlayerItem { pub item_id: u32, pub quantity: u32, pub score: i32, + pub test: (u32, u32), } #[derive(Drop, Serde)] @@ -85,6 +86,8 @@ pub struct PlayerConfig { pub name: ByteArray, pub items: Array, pub favorite_item: Option, + pub test: Array>>, + pub test_tuple: (u32, u32, PlayerItem), } #[derive(Drop, Serde)] From bb54e11a4e03b4332b4443d4d5f395e5aeb65801 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 18:40:21 +0700 Subject: [PATCH 36/41] ff --- crates/torii/core/src/sql/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index 7a40207e49..5a9ee03eb1 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -904,7 +904,7 @@ fn add_columns_recursive( let all_options = e.options.iter().map(|c| format!("'{}'", c.name)).collect::>().join(", "); - let sql_type = format!("TEXT CHECK({} IN ({}))", column_name, all_options); + let sql_type = format!("TEXT CHECK([{column_name}] IN ({all_options}))"); add_column(&column_name, &sql_type); for child in &e.options { From f06097560f62e9bbc9072f12bbbeb64f2417010e Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 18:49:39 +0700 Subject: [PATCH 37/41] fmt --- crates/torii/types-test/Scarb.lock | 2 +- crates/torii/types-test/manifest_dev.json | 1496 +++++++++++++++++++++ 2 files changed, 1497 insertions(+), 1 deletion(-) create mode 100644 crates/torii/types-test/manifest_dev.json diff --git a/crates/torii/types-test/Scarb.lock b/crates/torii/types-test/Scarb.lock index ffc9ecef4d..e8acab7a4a 100644 --- a/crates/torii/types-test/Scarb.lock +++ b/crates/torii/types-test/Scarb.lock @@ -14,7 +14,7 @@ version = "2.8.4" [[package]] name = "types_test" -version = "1.0.2" +version = "1.0.3" dependencies = [ "dojo", ] diff --git a/crates/torii/types-test/manifest_dev.json b/crates/torii/types-test/manifest_dev.json new file mode 100644 index 0000000000..48092ca366 --- /dev/null +++ b/crates/torii/types-test/manifest_dev.json @@ -0,0 +1,1496 @@ +{ + "world": { + "class_hash": "0x45575a88cc5cef1e444c77ce60b7b4c9e73a01cbbe20926d5a4c72a94011410", + "address": "0x1413cc3c0d35059f2807a2d77b8f61084dcac934144f24b4a72cf1d0ef9c7f6", + "seed": "types_test", + "name": "types test", + "entrypoints": [ + "uuid", + "set_metadata", + "register_namespace", + "register_event", + "register_model", + "register_contract", + "init_contract", + "upgrade_event", + "upgrade_model", + "upgrade_contract", + "emit_event", + "emit_events", + "set_entity", + "set_entities", + "delete_entity", + "delete_entities", + "grant_owner", + "revoke_owner", + "grant_writer", + "revoke_writer", + "upgrade" + ], + "abi": [ + { + "type": "impl", + "name": "World", + "interface_name": "dojo::world::iworld::IWorld" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "enum", + "name": "dojo::world::resource::Resource", + "variants": [ + { + "name": "Model", + "type": "(core::starknet::contract_address::ContractAddress, core::felt252)" + }, + { + "name": "Event", + "type": "(core::starknet::contract_address::ContractAddress, core::felt252)" + }, + { + "name": "Contract", + "type": "(core::starknet::contract_address::ContractAddress, core::felt252)" + }, + { + "name": "Namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "World", + "type": "()" + }, + { + "name": "Unregistered", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "dojo::model::metadata::ResourceMetadata", + "members": [ + { + "name": "resource_id", + "type": "core::felt252" + }, + { + "name": "metadata_uri", + "type": "core::byte_array::ByteArray" + }, + { + "name": "metadata_hash", + "type": "core::felt252" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::>", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::>" + } + ] + }, + { + "type": "enum", + "name": "dojo::model::definition::ModelIndex", + "variants": [ + { + "name": "Keys", + "type": "core::array::Span::" + }, + { + "name": "Id", + "type": "core::felt252" + }, + { + "name": "MemberId", + "type": "(core::felt252, core::felt252)" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "dojo::meta::layout::FieldLayout", + "members": [ + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "dojo::meta::layout::Layout", + "variants": [ + { + "name": "Fixed", + "type": "core::array::Span::" + }, + { + "name": "Struct", + "type": "core::array::Span::" + }, + { + "name": "Tuple", + "type": "core::array::Span::" + }, + { + "name": "Array", + "type": "core::array::Span::" + }, + { + "name": "ByteArray", + "type": "()" + }, + { + "name": "Enum", + "type": "core::array::Span::" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "dojo::world::iworld::IWorld", + "items": [ + { + "type": "function", + "name": "resource", + "inputs": [ + { + "name": "selector", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "dojo::world::resource::Resource" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "uuid", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u32" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "metadata", + "inputs": [ + { + "name": "resource_selector", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "dojo::model::metadata::ResourceMetadata" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_metadata", + "inputs": [ + { + "name": "metadata", + "type": "dojo::model::metadata::ResourceMetadata" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_namespace", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_event", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_model", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_contract", + "inputs": [ + { + "name": "salt", + "type": "core::felt252" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "init_contract", + "inputs": [ + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "init_calldata", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_event", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_model", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "upgrade_contract", + "inputs": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [ + { + "type": "core::starknet::class_hash::ClassHash" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "emit_event", + "inputs": [ + { + "name": "event_selector", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::" + }, + { + "name": "values", + "type": "core::array::Span::" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "emit_events", + "inputs": [ + { + "name": "event_selector", + "type": "core::felt252" + }, + { + "name": "keys", + "type": "core::array::Span::>" + }, + { + "name": "values", + "type": "core::array::Span::>" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "entity", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "index", + "type": "dojo::model::definition::ModelIndex" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "entities", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "indexes", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [ + { + "type": "core::array::Span::>" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_entity", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "index", + "type": "dojo::model::definition::ModelIndex" + }, + { + "name": "values", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_entities", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "indexes", + "type": "core::array::Span::" + }, + { + "name": "values", + "type": "core::array::Span::>" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "delete_entity", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "index", + "type": "dojo::model::definition::ModelIndex" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "delete_entities", + "inputs": [ + { + "name": "model_selector", + "type": "core::felt252" + }, + { + "name": "indexes", + "type": "core::array::Span::" + }, + { + "name": "layout", + "type": "dojo::meta::layout::Layout" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_owner", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_owner", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_owner", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "is_writer", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_writer", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_writer", + "inputs": [ + { + "name": "resource", + "type": "core::felt252" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableWorld", + "interface_name": "dojo::world::iworld::IUpgradeableWorld" + }, + { + "type": "interface", + "name": "dojo::world::iworld::IUpgradeableWorld", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "world_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::WorldSpawned", + "kind": "struct", + "members": [ + { + "name": "creator", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::WorldUpgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::NamespaceRegistered", + "kind": "struct", + "members": [ + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "hash", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ModelRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::EventRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ContractRegistered", + "kind": "struct", + "members": [ + { + "name": "name", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "namespace", + "type": "core::byte_array::ByteArray", + "kind": "key" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "salt", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ModelUpgraded", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "prev_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::EventUpgraded", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + }, + { + "name": "address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "prev_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ContractUpgraded", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::ContractInitialized", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "init_calldata", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::EventEmitted", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "system_address", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::MetadataUpdate", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "uri", + "type": "core::byte_array::ByteArray", + "kind": "data" + }, + { + "name": "hash", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreSetRecord", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "keys", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreUpdateRecord", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreUpdateMember", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "member_selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "values", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "selector", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::WriterUpdated", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::OwnerUpdated", + "kind": "struct", + "members": [ + { + "name": "resource", + "type": "core::felt252", + "kind": "key" + }, + { + "name": "contract", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::bool", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::world::world_contract::world::Event", + "kind": "enum", + "variants": [ + { + "name": "WorldSpawned", + "type": "dojo::world::world_contract::world::WorldSpawned", + "kind": "nested" + }, + { + "name": "WorldUpgraded", + "type": "dojo::world::world_contract::world::WorldUpgraded", + "kind": "nested" + }, + { + "name": "NamespaceRegistered", + "type": "dojo::world::world_contract::world::NamespaceRegistered", + "kind": "nested" + }, + { + "name": "ModelRegistered", + "type": "dojo::world::world_contract::world::ModelRegistered", + "kind": "nested" + }, + { + "name": "EventRegistered", + "type": "dojo::world::world_contract::world::EventRegistered", + "kind": "nested" + }, + { + "name": "ContractRegistered", + "type": "dojo::world::world_contract::world::ContractRegistered", + "kind": "nested" + }, + { + "name": "ModelUpgraded", + "type": "dojo::world::world_contract::world::ModelUpgraded", + "kind": "nested" + }, + { + "name": "EventUpgraded", + "type": "dojo::world::world_contract::world::EventUpgraded", + "kind": "nested" + }, + { + "name": "ContractUpgraded", + "type": "dojo::world::world_contract::world::ContractUpgraded", + "kind": "nested" + }, + { + "name": "ContractInitialized", + "type": "dojo::world::world_contract::world::ContractInitialized", + "kind": "nested" + }, + { + "name": "EventEmitted", + "type": "dojo::world::world_contract::world::EventEmitted", + "kind": "nested" + }, + { + "name": "MetadataUpdate", + "type": "dojo::world::world_contract::world::MetadataUpdate", + "kind": "nested" + }, + { + "name": "StoreSetRecord", + "type": "dojo::world::world_contract::world::StoreSetRecord", + "kind": "nested" + }, + { + "name": "StoreUpdateRecord", + "type": "dojo::world::world_contract::world::StoreUpdateRecord", + "kind": "nested" + }, + { + "name": "StoreUpdateMember", + "type": "dojo::world::world_contract::world::StoreUpdateMember", + "kind": "nested" + }, + { + "name": "StoreDelRecord", + "type": "dojo::world::world_contract::world::StoreDelRecord", + "kind": "nested" + }, + { + "name": "WriterUpdated", + "type": "dojo::world::world_contract::world::WriterUpdated", + "kind": "nested" + }, + { + "name": "OwnerUpdated", + "type": "dojo::world::world_contract::world::OwnerUpdated", + "kind": "nested" + } + ] + } + ] + }, + "contracts": [ + { + "address": "0x5e3b474c077756ba9378903764ba932bed6c9ffe000165f9beba8769e9e8200", + "class_hash": "0x148bf0bb34a0ed998edfbca7f0799e76c1c5bbb87ca972d684b60136550a02", + "abi": [ + { + "type": "impl", + "name": "records__ContractImpl", + "interface_name": "dojo::contract::interface::IContract" + }, + { + "type": "interface", + "name": "dojo::contract::interface::IContract", + "items": [] + }, + { + "type": "impl", + "name": "records__DeployedContractImpl", + "interface_name": "dojo::meta::interface::IDeployedResource" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "dojo::meta::interface::IDeployedResource", + "items": [ + { + "type": "function", + "name": "dojo_name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "RecordsImpl", + "interface_name": "types_test::contracts::IRecords" + }, + { + "type": "interface", + "name": "types_test::contracts::IRecords", + "items": [ + { + "type": "function", + "name": "create", + "inputs": [ + { + "name": "num_records", + "type": "core::integer::u8" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "delete", + "inputs": [ + { + "name": "record_id", + "type": "core::integer::u32" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "function", + "name": "dojo_init", + "inputs": [], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "impl", + "name": "WorldProviderImpl", + "interface_name": "dojo::contract::components::world_provider::IWorldProvider" + }, + { + "type": "struct", + "name": "dojo::world::iworld::IWorldDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "dojo::contract::components::world_provider::IWorldProvider", + "items": [ + { + "type": "function", + "name": "world_dispatcher", + "inputs": [], + "outputs": [ + { + "type": "dojo::world::iworld::IWorldDispatcher" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "UpgradeableImpl", + "interface_name": "dojo::contract::components::upgradeable::IUpgradeable" + }, + { + "type": "interface", + "name": "dojo::contract::components::upgradeable::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [] + }, + { + "type": "event", + "name": "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "dojo::contract::components::upgradeable::upgradeable_cpt::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "types_test::contracts::records::Event", + "kind": "enum", + "variants": [ + { + "name": "UpgradeableEvent", + "type": "dojo::contract::components::upgradeable::upgradeable_cpt::Event", + "kind": "nested" + }, + { + "name": "WorldProviderEvent", + "type": "dojo::contract::components::world_provider::world_provider_cpt::Event", + "kind": "nested" + } + ] + } + ], + "init_calldata": [], + "tag": "types_test-records", + "selector": "0x11bb34944de6c1b172f8f43624c026a63b2913b825f996c99c85ea3d1f9ac8a", + "systems": [ + "create", + "delete", + "upgrade" + ] + } + ], + "models": [ + { + "members": [], + "class_hash": "0x21569c1c7a59f12d1b1d4c14a4447e21c9783c67327caac8853563bb4cc76d", + "tag": "types_test-Record", + "selector": "0x21aeac4d33b64ae624204430d0c09837f34f654a7fac1cc83270a27a739ec71" + }, + { + "members": [], + "class_hash": "0x55a80dd8db0bc8ead4fc1f4c33c76367dbd63507e71672fe7af7ad8dbeac6af", + "tag": "types_test-RecordSibling", + "selector": "0x298128bd79a29d95f2888d2284ff39e037072dd71dae9313ffee995b12e9c86" + }, + { + "members": [], + "class_hash": "0x28299f3de912dbe3777bb8c3dd7e263a0d7785ac19fda69ca35f7e2392f7854", + "tag": "types_test-Subrecord", + "selector": "0x38871602c0e0b8a9c403655653d0080376ce1706b8b00c63a5f850cd6709689" + } + ], + "events": [ + { + "members": [], + "class_hash": "0x4d455c1ea98a2dcb6af77a01b3b992acfd80478bcb4948a663fdef69ae0b4d0", + "tag": "types_test-RecordLogged", + "selector": "0x26d4239a31cc102ac8fe5df0e0af32e84fa6d8667c699fbb5a41ce39ad713b4" + } + ] +} \ No newline at end of file From 70b3eb34eb633e9a2a01842e7d65c495bf8d9453 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 18:57:42 +0700 Subject: [PATCH 38/41] c --- examples/spawn-and-move/manifest_dev.json | 6 +++--- examples/spawn-and-move/src/actions.cairo | 11 +++-------- examples/spawn-and-move/src/models.cairo | 3 --- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/examples/spawn-and-move/manifest_dev.json b/examples/spawn-and-move/manifest_dev.json index 291337ef74..057ee7d64d 100644 --- a/examples/spawn-and-move/manifest_dev.json +++ b/examples/spawn-and-move/manifest_dev.json @@ -1252,8 +1252,8 @@ }, "contracts": [ { - "address": "0x2e0a8cf3c5d1bc2a4f3ad4e401b50c9b19c490e5b33bfd333d73b3c72669b7f", - "class_hash": "0x40aa706a235ab9f4ce4c415e140856e737834de42978cecd96bf2f647ff2957", + "address": "0x7e8a52c68b243d3a86a55c04ccec2edc760252d5952566d3af4001bd6cf38f3", + "class_hash": "0x636c2cf31b094097625cb5ada96f54ee9a3f7bc6d8cde00cc85e5ef0c622c8b", "abi": [ { "type": "impl", @@ -2167,7 +2167,7 @@ }, { "members": [], - "class_hash": "0x78a98614cad448d6a653492736190834e0318f7330e67c648a8d06ebb387460", + "class_hash": "0x77018639b9a36824fe474c7c2b2ba7b9ef0f30fa54be1d1f62037dbfc01a89b", "tag": "ns-PlayerConfig", "selector": "0x3bea561c3e142a660a00d1471d7712b70695dc4ee3b173aeaefd5178f7a21af" }, diff --git a/examples/spawn-and-move/src/actions.cairo b/examples/spawn-and-move/src/actions.cairo index 9654eb6b2a..95c8d4a4d5 100644 --- a/examples/spawn-and-move/src/actions.cairo +++ b/examples/spawn-and-move/src/actions.cairo @@ -65,8 +65,6 @@ pub mod actions { name: "hello", items: array![], favorite_item: Option::None, - test: array![], - test_tuple: (1, 2, PlayerItem { item_id: 1, quantity: 100, score: 150, test: (1, 2) }) }; let mut world = self.world_default(); @@ -125,14 +123,11 @@ pub mod actions { let player = get_caller_address(); let items = array![ - PlayerItem { item_id: 1, quantity: 100, score: 150, test: (1, 2) }, - PlayerItem { item_id: 2, quantity: 50, score: -32, test: (3, 4) } + PlayerItem { item_id: 1, quantity: 100, score: 150 }, + PlayerItem { item_id: 2, quantity: 50, score: -32 } ]; - let item = items[0].clone(); - let config = PlayerConfig { player, name, items, favorite_item: Option::Some(1), test: array![ - array![Option::Some(item)], - ], test_tuple: (1, 2, item) }; + let config = PlayerConfig { player, name, items, favorite_item: Option::Some(1) }; world.write_model(@config); } diff --git a/examples/spawn-and-move/src/models.cairo b/examples/spawn-and-move/src/models.cairo index bb7ea288e4..fc389d2cc9 100644 --- a/examples/spawn-and-move/src/models.cairo +++ b/examples/spawn-and-move/src/models.cairo @@ -75,7 +75,6 @@ pub struct PlayerItem { pub item_id: u32, pub quantity: u32, pub score: i32, - pub test: (u32, u32), } #[derive(Drop, Serde)] @@ -86,8 +85,6 @@ pub struct PlayerConfig { pub name: ByteArray, pub items: Array, pub favorite_item: Option, - pub test: Array>>, - pub test_tuple: (u32, u32, PlayerItem), } #[derive(Drop, Serde)] From a4ab8b5d8e236c236e0a91727e1f8a1da2de3f28 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 19:39:37 +0700 Subject: [PATCH 39/41] fix clauses --- crates/torii/graphql/src/query/data.rs | 35 ++++------ crates/torii/graphql/src/query/filter.rs | 10 +-- crates/torii/grpc/src/server/mod.rs | 87 +++++++++++------------- 3 files changed, 57 insertions(+), 75 deletions(-) diff --git a/crates/torii/graphql/src/query/data.rs b/crates/torii/graphql/src/query/data.rs index 5cf1fb5c76..c01895730b 100644 --- a/crates/torii/graphql/src/query/data.rs +++ b/crates/torii/graphql/src/query/data.rs @@ -5,7 +5,7 @@ use torii_core::constants::WORLD_CONTRACT_TYPE; use super::filter::{Filter, FilterValue}; use super::order::{CursorDirection, Direction, Order}; -use crate::constants::{DEFAULT_LIMIT, MODEL_TABLE}; +use crate::constants::DEFAULT_LIMIT; use crate::object::connection::{cursor, ConnectionArguments}; pub async fn count_rows( @@ -87,10 +87,7 @@ pub async fn fetch_multiple_rows( // `first` or `last` param. Explicit ordering take precedence match order { Some(order) => { - let mut column_name = order.field.clone(); - if table_name != MODEL_TABLE { - column_name = format!("external_{}", column_name); - } + let column_name = order.field.clone(); query.push_str(&format!( " ORDER BY {column_name} {}, {id_column} {} LIMIT {limit}", order.direction.as_ref(), @@ -127,9 +124,10 @@ pub async fn fetch_multiple_rows( Ok((data, page_info)) } else if is_cursor_based { let order_field = match order { - Some(order) => format!("external_{}", order.field), + Some(order) => order.field.clone(), None => id_column.to_string(), }; + match cursor_param { Some(cursor_query) => { let first_cursor = cursor::encode( @@ -190,20 +188,17 @@ fn handle_cursor( ) -> Result { match cursor::decode(cursor) { Ok((event_id, field_value)) => match order { - Some(order) => { - let field_name = format!("external_{}", order.field); - Ok(format!( - "(({} {} '{}' AND {} = '{}') OR {} {} '{}')", - id_column, - direction.as_ref(), - event_id, - field_name, - field_value, - field_name, - direction.as_ref(), - field_value - )) - } + Some(order) => Ok(format!( + "(({} {} '{}' AND {} = '{}') OR {} {} '{}')", + id_column, + direction.as_ref(), + event_id, + order.field, + field_value, + order.field, + direction.as_ref(), + field_value + )), None => Ok(format!("{} {} '{}'", id_column, direction.as_ref(), event_id)), }, Err(_) => Err(sqlx::Error::Decode("Invalid cursor format".into())), diff --git a/crates/torii/graphql/src/query/filter.rs b/crates/torii/graphql/src/query/filter.rs index 54e14b768c..0f39c073dc 100644 --- a/crates/torii/graphql/src/query/filter.rs +++ b/crates/torii/graphql/src/query/filter.rs @@ -56,16 +56,10 @@ pub struct Filter { pub fn parse_filter(input: &Name, value: FilterValue) -> Filter { for comparator in Comparator::iter() { if let Some(field) = input.strip_suffix(comparator.as_ref()) { - // Filtering only applies to model members which are stored in db with - // external_{name} - return Filter { - field: format!("external_{}", field), - comparator: comparator.clone(), - value, - }; + return Filter { field: field.to_string(), comparator: comparator.clone(), value }; } } // If no suffix found assume equality comparison - Filter { field: format!("external_{}", input), comparator: Comparator::Eq, value } + Filter { field: input.to_string(), comparator: Comparator::Eq, value } } diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index 39a859ee1d..4cfda84941 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -674,19 +674,15 @@ impl DojoWorld { let schemas = self.model_cache.models(&model_ids).await?.into_iter().map(|m| m.schema).collect(); - let model = member_clause.model.clone(); - let parts: Vec<&str> = member_clause.member.split('.').collect(); - let (table_name, column_name) = if parts.len() > 1 { - let nested_table = parts[..parts.len() - 1].join("$"); - (format!("{model}${nested_table}"), format!("external_{}", parts.last().unwrap())) - } else { - (model, format!("external_{}", member_clause.member)) - }; + // Use the member name directly as the column name since it's already flattened let (entity_query, count_query) = build_sql_query( &schemas, table, entity_relation_column, - Some(&format!("[{table_name}].{column_name} {comparison_operator} ?")), + Some(&format!( + "[{}].[{}] {comparison_operator} ?", + member_clause.model, member_clause.member + )), limit, offset, )?; @@ -697,7 +693,7 @@ impl DojoWorld { .await? .unwrap_or(0); let db_entities = sqlx::query(&entity_query) - .bind(comparison_value.clone()) + .bind(comparison_value) .bind(limit) .bind(offset) .fetch_all(&self.pool) @@ -1101,9 +1097,7 @@ fn build_composite_clause( let mut join_clauses = Vec::new(); let mut having_clauses = Vec::new(); let mut bind_values = Vec::new(); - - // HashMap to track the number of joins per model - let mut model_counters: HashMap = HashMap::new(); + let mut seen_models = HashMap::new(); for clause in &composite.clauses { match clause.clause_type.as_ref().unwrap() { @@ -1140,42 +1134,41 @@ fn build_composite_clause( bind_values.push(comparison_value); let model = member.model.clone(); - let parts: Vec<&str> = member.member.split('.').collect(); - let (table_name, column_name) = if parts.len() > 1 { - let nested_table = parts[..parts.len() - 1].join("$"); - ( - format!("[{model}${nested_table}]"), - format!("external_{}", parts.last().unwrap()), - ) - } else { - (format!("[{model}]"), format!("external_{}", member.member)) - }; - - let (namespace, model_name) = member - .model - .split_once('-') - .ok_or(QueryError::InvalidNamespacedModel(member.model.clone()))?; - let model_id = compute_selector_from_names(namespace, model_name); - - // Generate a unique alias for each model - let counter = model_counters.entry(model.clone()).or_insert(0); - *counter += 1; - let alias = - if *counter == 1 { model.clone() } else { format!("{model}_{}", *counter - 1) }; - - join_clauses.push(format!( - "LEFT JOIN {table_name} AS [{alias}] ON [{table}].id = [{alias}].entity_id" - )); - where_clauses.push(format!("[{alias}].{column_name} {comparison_operator} ?")); - having_clauses.push(format!( - "INSTR(group_concat({model_relation_table}.model_id), '{:#x}') > 0", - model_id - )); + // Get or create unique alias for this model + let alias = seen_models.entry(model.clone()).or_insert_with(|| { + let (namespace, model_name) = model + .split_once('-') + .ok_or(QueryError::InvalidNamespacedModel(model.clone())) + .unwrap(); + let model_id = compute_selector_from_names(namespace, model_name); + + // Add model check to having clause + having_clauses.push(format!( + "INSTR(group_concat({model_relation_table}.model_id), '{:#x}') > 0", + model_id + )); + + // Add join clause + join_clauses.push(format!( + "LEFT JOIN [{model}] AS [{model}] ON [{table}].id = \ + [{model}].internal_entity_id" + )); + + model.clone() + }); + + // Use the column name directly since it's already flattened + where_clauses + .push(format!("[{alias}].[{}] {comparison_operator} ?", member.member)); } - ClauseType::Composite(nested_composite) => { + ClauseType::Composite(nested) => { + // Handle nested composite by recursively building the clause let (nested_where, nested_having, nested_join, nested_values) = - build_composite_clause(table, model_relation_table, nested_composite)?; - where_clauses.push(format!("({})", nested_where.trim_start_matches("WHERE "))); + build_composite_clause(table, model_relation_table, nested)?; + + if !nested_where.is_empty() { + where_clauses.push(format!("({})", nested_where.trim_start_matches("WHERE "))); + } if !nested_having.is_empty() { having_clauses.push(nested_having.trim_start_matches("HAVING ").to_string()); } From 786348d99b44d951e21e457ec5206f076aac2dcc Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 20:10:02 +0700 Subject: [PATCH 40/41] fix libp2p --- crates/torii/libp2p/src/server/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/torii/libp2p/src/server/mod.rs b/crates/torii/libp2p/src/server/mod.rs index 85b81ade16..95216e8e18 100644 --- a/crates/torii/libp2p/src/server/mod.rs +++ b/crates/torii/libp2p/src/server/mod.rs @@ -252,10 +252,8 @@ impl Relay

{ let model_id = ty_model_id(&ty).unwrap(); // select only identity field, if doesn't exist, empty string - let query = format!( - "SELECT external_identity FROM [{}] WHERE id = ?", - ty.name() - ); + let query = + format!("SELECT identity FROM [{}] WHERE id = ?", ty.name()); let entity_identity: Option = match sqlx::query_scalar(&query) .bind(format!("{:#x}", entity_id)) .fetch_optional(&mut *pool) From 5e04d91a9967ebf6c06d44286eb4a2ea41130211 Mon Sep 17 00:00:00 2001 From: Nasr Date: Mon, 2 Dec 2024 20:28:30 +0700 Subject: [PATCH 41/41] fix id --- crates/torii/libp2p/src/server/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/torii/libp2p/src/server/mod.rs b/crates/torii/libp2p/src/server/mod.rs index 95216e8e18..704cff6170 100644 --- a/crates/torii/libp2p/src/server/mod.rs +++ b/crates/torii/libp2p/src/server/mod.rs @@ -252,8 +252,10 @@ impl Relay

{ let model_id = ty_model_id(&ty).unwrap(); // select only identity field, if doesn't exist, empty string - let query = - format!("SELECT identity FROM [{}] WHERE id = ?", ty.name()); + let query = format!( + "SELECT identity FROM [{}] WHERE internal_id = ?", + ty.name() + ); let entity_identity: Option = match sqlx::query_scalar(&query) .bind(format!("{:#x}", entity_id)) .fetch_optional(&mut *pool)