diff --git a/bins/torii-introspect-bin/src/main.rs b/bins/torii-introspect-bin/src/main.rs index 96f523eb..ea102237 100644 --- a/bins/torii-introspect-bin/src/main.rs +++ b/bins/torii-introspect-bin/src/main.rs @@ -719,22 +719,6 @@ async fn run_with_postgres( let decoder: Arc = Arc::new(decoder); - let (erc20_url, erc721_url, erc1155_url) = - ecs_token_storage_urls(token_db_setup.as_ref(), installed_token_support); - let ecs_sink = EcsSink::new( - storage_database_url, - config.max_db_connections, - erc20_url, - erc721_url, - erc1155_url, - contract_type_registry.clone(), - config.from_block, - config.index_external_contracts, - installed_external_decoders.clone(), - ) - .await?; - let ecs_grpc_service = ecs_sink.get_grpc_service_impl(); - let reflection_builder = ReflectionBuilder::configure() .register_encoded_file_descriptor_set(torii::TORII_DESCRIPTOR_SET) .register_encoded_file_descriptor_set(ECS_DESCRIPTOR_SET); @@ -750,7 +734,6 @@ async fn run_with_postgres( .engine_database_url(engine_database_url) .with_extractor(extractor) .add_decoder(decoder) - .add_sink_boxed(Box::new(ecs_sink)) .add_sink_boxed(Box::new( OrderedSinkPipeline::new("introspect-projection-pipeline") .push(Box::new(introspect_sink)) @@ -799,6 +782,23 @@ async fn run_with_postgres( ) .await?; + let (erc20_url, erc721_url, erc1155_url) = + ecs_token_storage_urls(token_db_setup.as_ref(), installed_token_support); + let ecs_sink = EcsSink::new( + storage_database_url, + config.max_db_connections, + erc20_url, + erc721_url, + erc1155_url, + contract_type_registry.clone(), + config.from_block, + config.index_external_contracts, + installed_external_decoders.clone(), + ) + .await?; + let ecs_grpc_service = ecs_sink.get_grpc_service_impl(); + let torii_config = torii_config.add_sink_boxed(Box::new(ecs_sink)); + let reflection = reflection_builder .build_v1() .expect("failed to build reflection service") @@ -913,22 +913,6 @@ async fn run_with_sqlite( let decoder: Arc = Arc::new(decoder); - let (erc20_url, erc721_url, erc1155_url) = - ecs_token_storage_urls(token_db_setup.as_ref(), installed_token_support); - let ecs_sink = EcsSink::new( - storage_database_url, - config.max_db_connections, - erc20_url, - erc721_url, - erc1155_url, - contract_type_registry.clone(), - config.from_block, - config.index_external_contracts, - installed_external_decoders.clone(), - ) - .await?; - let ecs_grpc_service = ecs_sink.get_grpc_service_impl(); - let reflection_builder = ReflectionBuilder::configure() .register_encoded_file_descriptor_set(torii::TORII_DESCRIPTOR_SET) .register_encoded_file_descriptor_set(ECS_DESCRIPTOR_SET); @@ -944,7 +928,6 @@ async fn run_with_sqlite( .engine_database_url(engine_database_url) .with_extractor(extractor) .add_decoder(decoder) - .add_sink_boxed(Box::new(ecs_sink)) .add_sink_boxed(Box::new( OrderedSinkPipeline::new("introspect-projection-pipeline") .push(Box::new(IntrospectSqliteDb::new(pool.clone(), ()))) @@ -993,6 +976,23 @@ async fn run_with_sqlite( ) .await?; + let (erc20_url, erc721_url, erc1155_url) = + ecs_token_storage_urls(token_db_setup.as_ref(), installed_token_support); + let ecs_sink = EcsSink::new( + storage_database_url, + config.max_db_connections, + erc20_url, + erc721_url, + erc1155_url, + contract_type_registry.clone(), + config.from_block, + config.index_external_contracts, + installed_external_decoders.clone(), + ) + .await?; + let ecs_grpc_service = ecs_sink.get_grpc_service_impl(); + let torii_config = torii_config.add_sink_boxed(Box::new(ecs_sink)); + let reflection = reflection_builder .build_v1() .expect("failed to build reflection service") diff --git a/crates/torii-ecs-sink/src/grpc_service.rs b/crates/torii-ecs-sink/src/grpc_service.rs index 5013a67c..565828cf 100644 --- a/crates/torii-ecs-sink/src/grpc_service.rs +++ b/crates/torii-ecs-sink/src/grpc_service.rs @@ -4943,7 +4943,22 @@ async fn attach_sqlite_database( return Ok(()); } - let path = sqlite_db_path(url).replace('\'', "''"); + let db_path = sqlite_db_path(url); + if !is_sqlite_memory_url(url) { + let path = std::path::Path::new(&db_path); + if let Some(parent) = path.parent().filter(|path| !path.as_os_str().is_empty()) { + std::fs::create_dir_all(parent)?; + } + if !path.exists() { + std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(false) + .open(path)?; + } + } + + let path = db_path.replace('\'', "''"); // sqlite-dynamic-ok: ATTACH requires the database path and schema identifier in SQL text. sqlx::query(&format!("ATTACH DATABASE '{path}' AS {schema}")) .execute(&mut *conn) @@ -4951,6 +4966,11 @@ async fn attach_sqlite_database( Ok(()) } +fn is_sqlite_memory_url(url: &str) -> bool { + matches!(url, ":memory:" | "sqlite::memory:") + || (url.starts_with("sqlite:file:") && url.contains("mode=memory")) +} + fn sqlite_master_preview_sql(schema: &str) -> &'static str { match schema { "erc20" => "SELECT name FROM erc20.sqlite_master WHERE type='table' LIMIT 5", @@ -8736,6 +8756,34 @@ mod tests { ); } + #[tokio::test] + async fn init_skips_missing_sqlite_token_databases() { + let nonce = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("clock before epoch") + .as_nanos(); + let db_path = std::env::temp_dir().join(format!("torii-ecs-main-{nonce}.db")); + std::fs::File::create(&db_path).expect("create main db file"); + let db_path = db_path.display().to_string(); + let missing_erc20 = temp_sqlite_url("missing-token-db-erc20"); + + let service = EcsService::new(&db_path, Some(1), Some(&missing_erc20), None, None) + .await + .expect("service init"); + + service + .attach_erc_databases() + .await + .expect("missing token db should be skipped"); + + sqlx::query("CREATE TABLE erc20.balances (id INTEGER PRIMARY KEY)") + .execute(&service.state.pool) + .await + .expect("missing token db should be attached and writable"); + + assert!(service.state.has_erc20); + } + #[tokio::test] async fn retrieve_controllers_reads_backing_table() { let db_path = test_db_path("retrieve-controllers");