From 3f7cd6353a60f918b3978822b2a34cd1f8419bf5 Mon Sep 17 00:00:00 2001 From: Arthur Roberts Date: Wed, 20 Aug 2025 21:07:37 +0100 Subject: [PATCH] Moved string matching to sqlite Probably (definitely more efficient) --- scryfall_deser/Cargo.toml | 1 + scryfall_deser/src/db.rs | 53 +++++++++++++++++++++++++++++++------- scryfall_deser/src/lib.rs | 4 +-- scryfall_deser/src/main.rs | 16 ++++-------- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/scryfall_deser/Cargo.toml b/scryfall_deser/Cargo.toml index 9b595ec..7e9c0f1 100644 --- a/scryfall_deser/Cargo.toml +++ b/scryfall_deser/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" chrono = { version = "0.4.39", features = ["serde"] } clap = { version = "4.5.42", features = ["derive"] } closestmatch = "0.1.2" +deunicode = "1.6.2" dir_spec = "0.2.0" rusqlite = "0.37.0" serde = { version = "1.0", features = ["derive"] } diff --git a/scryfall_deser/src/db.rs b/scryfall_deser/src/db.rs index a9565e8..a08df34 100644 --- a/scryfall_deser/src/db.rs +++ b/scryfall_deser/src/db.rs @@ -1,8 +1,8 @@ +use deunicode::deunicode; use rusqlite; use std::fmt; use std::fs; use std::path::PathBuf; -use std::str::SplitWhitespace; use super::deser::ScryfallCard; use super::utils::get_local_cache_folder; @@ -46,13 +46,14 @@ pub fn update_db_with_file(file: PathBuf) -> bool { let tx = conn.transaction().unwrap(); for card in ac { for word in card.name.split_whitespace() { + let word = deunicode(word); let res = tx.execute( "INSERT INTO magic_words (word) VALUES (?1) ON CONFLICT (word) DO NOTHING;", [word.replace(",", "")], ); } - let lowercase_name = card.name.to_lowercase(); + let lowercase_name = deunicode(&card.name.to_lowercase()); let power_toughness = match card.power { Some(p) => Some(format!("{}/{}", p, card.toughness.unwrap())), None => None, @@ -107,6 +108,7 @@ impl fmt::Display for DbCard { #[derive(Debug)] pub struct DbCard { pub name: String, + pub lowercase_name: String, pub type_line: String, pub oracle_text: Option, pub power_toughness: Option, @@ -125,11 +127,11 @@ pub fn get_card_by_name(name: &str, name_type: GetNameType) -> Option { let conn = rusqlite::Connection::open(sqlite_file).unwrap(); let sql = match name_type { GetNameType::Name => { - "SELECT name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri + "SELECT name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri FROM cards WHERE name = (?1)" } GetNameType::LowercaseName => { - "SELECT name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri + "SELECT name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri FROM cards WHERE lowercase_name = (?1)" } }; @@ -138,17 +140,48 @@ pub fn get_card_by_name(name: &str, name_type: GetNameType) -> Option { match rows.next().unwrap() { Some(row) => Some(DbCard { name: row.get(0).unwrap(), - type_line: row.get(1).unwrap(), - oracle_text: row.get(2).unwrap(), - power_toughness: row.get(3).unwrap(), - loyalty: row.get(4).unwrap(), - mana_cost: row.get(5).unwrap(), - scryfall_uri: row.get(6).unwrap(), + lowercase_name: row.get(1).unwrap(), + type_line: row.get(2).unwrap(), + oracle_text: row.get(3).unwrap(), + power_toughness: row.get(4).unwrap(), + loyalty: row.get(5).unwrap(), + mana_cost: row.get(6).unwrap(), + scryfall_uri: row.get(7).unwrap(), }), None => None, } } +pub fn find_matching_cards(name: &str) -> Vec { + let sqlite_file = get_local_data_sqlite_file(); + let conn = rusqlite::Connection::open(sqlite_file).unwrap(); + // There must be something better than this - although I don't think it's possible with a str + let mut name = name.to_string(); + name.push('%'); + name.insert(0, '%'); + let mut stmt = conn + .prepare( + "SELECT name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri + FROM cards WHERE lowercase_name LIKE (?1)", + ) + .unwrap(); + stmt.query_map([name], |row| { + Ok(DbCard { + name: row.get(0).unwrap(), + lowercase_name: row.get(1).unwrap(), + type_line: row.get(2).unwrap(), + oracle_text: row.get(3).unwrap(), + power_toughness: row.get(4).unwrap(), + loyalty: row.get(5).unwrap(), + mana_cost: row.get(6).unwrap(), + scryfall_uri: row.get(7).unwrap(), + }) + }) + .unwrap() + .filter_map(|res| res.ok()) + .collect() +} + const CREATE_CARDS_TABLE_SQL: &str = " CREATE TABLE cards ( name TEXT NOT NULL UNIQUE, diff --git a/scryfall_deser/src/lib.rs b/scryfall_deser/src/lib.rs index 1cacae4..80bc452 100644 --- a/scryfall_deser/src/lib.rs +++ b/scryfall_deser/src/lib.rs @@ -6,8 +6,8 @@ pub use crate::deser::ScryfallCard; mod db; pub use db::{ - get_all_card_names, get_all_lowercase_card_names, get_card_by_name, init_db, - update_db_with_file, GetNameType, + find_matching_cards, get_all_card_names, get_all_lowercase_card_names, get_card_by_name, + init_db, update_db_with_file, GetNameType, }; mod utils; diff --git a/scryfall_deser/src/main.rs b/scryfall_deser/src/main.rs index 21f0e33..4a2f476 100644 --- a/scryfall_deser/src/main.rs +++ b/scryfall_deser/src/main.rs @@ -1,4 +1,5 @@ use clap::Parser; +use scryfall_deser::find_matching_cards; use scryfall_deser::get_all_lowercase_card_names; use scryfall_deser::get_card_by_name; use scryfall_deser::get_local_cache_folder; @@ -55,26 +56,19 @@ fn main() { } search_string.pop(); - // This section should be replaced with a SQL command - let cards = get_all_lowercase_card_names(); - - let mut matching_cards = Vec::new(); - for card in cards { - if card.contains(&search_string) { - matching_cards.push(card.clone()); - } - } + let matching_cards = find_matching_cards(&search_string); dbg!(&matching_cards); + if matching_cards.is_empty() { // Do some distance checking stuff } else if matching_cards.len() == 1 { - let card = get_card_by_name(&matching_cards[0], GetNameType::LowercaseName); + let card = get_card_by_name(&matching_cards[0].name, GetNameType::LowercaseName); dbg!(card); } else { for card in matching_cards { println!( "{}", - get_card_by_name(&card, GetNameType::LowercaseName) + get_card_by_name(&card.lowercase_name, GetNameType::LowercaseName) .unwrap() .name );