geolite 0.0.1

open source geocode solution
use rusqlite::Connection;

const SQL_CREATE: &str = "
  CREATE TABLE IF NOT EXISTS admin_levels_hierarchy (
    admin_level_id INTEGER PRIMARY KEY REFERENCES admin_levels(id) ON DELETE CASCADE,
    ancestor_ids BLOB NOT NULL,
    user_friendly_name TEXT NOT NULL
  );
";

const SQL_DROP: &str = "DROP TABLE IF EXISTS admin_levels_hierarchy;";

const SQL_COUNT: &str = "
  SELECT COUNT(*) FROM admin_levels_hierarchy
";

const SQL_INSERT: &str = "
  INSERT OR IGNORE INTO admin_levels_hierarchy (
    admin_level_id,
    ancestor_ids,
    user_friendly_name
  ) VALUES (
    ?1,
    jsonb(?2),
    ?3
  );
";

impl_table_ops!(pub(super), SQL_CREATE, SQL_DROP);

pub struct hierarchy_row {
  pub admin_level_id: i64,
  pub ancestor_ids: String,
  pub user_friendly_name: String,
}

pub struct hierarchy_lookup_row {
  pub user_friendly_name: String,
  pub ancestor_ids: Vec<i64>,
}

pub fn destroy(conn: &Connection) {
  drop_table(conn);
  create_table(conn);
}

pub fn count(conn: &Connection) -> i64 {
  conn
    .query_row(SQL_COUNT, [], |row| row.get(0))
    .expect("failed to count admin_levels_hierarchy")
}

pub fn load_by_ids(
  conn: &Connection,
  ids: &[i64],
) -> std::collections::HashMap<i64, hierarchy_lookup_row> {
  if ids.is_empty() {
    return std::collections::HashMap::new();
  }
  let placeholders = ids.iter().map(|_| "?").collect::<Vec<_>>().join(",");
  let sql = format!(
    "SELECT admin_level_id, user_friendly_name, json(ancestor_ids)
         FROM admin_levels_hierarchy
         WHERE admin_level_id IN ({placeholders})"
  );
  let params: Vec<rusqlite::types::Value> = ids
    .iter()
    .map(|&id| rusqlite::types::Value::Integer(id))
    .collect();
  let mut stmt = conn
    .prepare(&sql)
    .expect("failed to prepare hierarchy load_by_ids");
  stmt
    .query_map(rusqlite::params_from_iter(params.iter()), |row| {
      let json_str: String = row.get(2)?;
      Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?, json_str))
    })
    .expect("failed to query hierarchy load_by_ids")
    .filter_map(|r| r.ok())
    .map(|(id, name, json_str)| {
      let ancestor_ids: Vec<i64> = serde_json::from_str(&json_str).unwrap_or_default();
      (
        id,
        hierarchy_lookup_row {
          user_friendly_name: name,
          ancestor_ids,
        },
      )
    })
    .collect()
}

pub fn batch_insert(conn: &Connection, rows: &[hierarchy_row]) {
  if rows.is_empty() {
    return;
  }
  let tx = conn
    .unchecked_transaction()
    .expect("failed to begin transaction");
  {
    let mut stmt = tx
      .prepare(SQL_INSERT)
      .expect("failed to prepare hierarchy insert");
    rows
      .iter()
      .try_for_each(|row| {
        stmt
          .execute(rusqlite::params![
            row.admin_level_id,
            row.ancestor_ids,
            row.user_friendly_name
          ])
          .map(|_| ())
      })
      .expect("failed to insert hierarchy row");
  }
  tx.commit().expect("failed to commit");
}