# SQLite

### 创建 SQLite 数据库

使用 `rusqlite` 可以创建 SQLite 数据库，Connection::open 会尝试打开一个数据库，若不存在，则创建新的数据库。

> 这里创建的 `cats.db` 数据库将被后面的例子所使用

    use rusqlite::{Connection, Result};
    use rusqlite::NO_PARAMS;

    fn main() -> Result<()> {
        let conn = Connection::open("cats.db")?;

        conn.execute(
            "create table if not exists cat_colors (
                 id integer primary key,
                 name text not null unique
             )",
            NO_PARAMS,
        )?;
        conn.execute(
            "create table if not exists cats (
                 id integer primary key,
                 name text not null,
                 color_id integer not null references cat_colors(id)
             )",
            NO_PARAMS,
        )?;

        Ok(())
    }

### 插入和查询

    use rusqlite::NO_PARAMS;
    use rusqlite::{Connection, Result};
    use std::collections::HashMap;

    #[derive(Debug)]
    struct Cat {
        name: String,
        color: String,
    }

    fn main() -> Result<()> {
        // 打开第一个例子所创建的数据库
        let conn = Connection::open("cats.db")?;

        let mut cat_colors = HashMap::new();
        cat_colors.insert(String::from("Blue"), vec!["Tigger", "Sammy"]);
        cat_colors.insert(String::from("Black"), vec!["Oreo", "Biscuit"]);

        for (color, catnames) in &cat_colors {
            // 插入一条数据行
            conn.execute(
                "INSERT INTO cat_colors (name) values (?1)",
                &[&color.to_string()],
            )?;
            // 获取最近插入数据行的 id
            let last_id: String = conn.last_insert_rowid().to_string();

            for cat in catnames {
                conn.execute(
                    "INSERT INTO cats (name, color_id) values (?1, ?2)",
                    &[&cat.to_string(), &last_id],
                )?;
            }
        }
        let mut stmt = conn.prepare(
            "SELECT c.name, cc.name from cats c
             INNER JOIN cat_colors cc
             ON cc.id = c.color_id;",
        )?;

        let cats = stmt.query_map(NO_PARAMS, |row| {
            Ok(Cat {
                name: row.get(0)?,
                color: row.get(1)?,
            })
        })?;

        for cat in cats {
            println!("Found cat {:?}", cat);
        }

        Ok(())
    }

### 使用事务

使用 Connection::transaction 可以开始新的事务，若没有对事务进行显式地提交 Transaction::commit，则会进行回滚。

下面的例子中，`rolled_back_tx` 插入了重复的颜色名称，会发生回滚。

    use rusqlite::{Connection, Result, NO_PARAMS};

    fn main() -> Result<()> {
        // 打开第一个例子所创建的数据库
        let mut conn = Connection::open("cats.db")?;

        successful_tx(&mut conn)?;

        let res = rolled_back_tx(&mut conn);
        assert!(res.is_err());

        Ok(())
    }

    fn successful_tx(conn: &mut Connection) -> Result<()> {
        let tx = conn.transaction()?;

        tx.execute("delete from cat_colors", NO_PARAMS)?;
        tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?;
        tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?;

        tx.commit()
    }

    fn rolled_back_tx(conn: &mut Connection) -> Result<()> {
        let tx = conn.transaction()?;

        tx.execute("delete from cat_colors", NO_PARAMS)?;
        tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?;
        tx.execute("insert into cat_colors (name) values (?1)", &[&"blue"])?;
        tx.execute("insert into cat_colors (name) values (?1)", &[&"lavender"])?;

        tx.commit()
    }