RustからSQLiteにデータを詰め込み、取り出すぞい
はじめに
EDINET APIで取得するデータをSQLiteに詰め込んでいきたいなと思っています。 ToDOアプリのサンプルがあったのでそれを見ていきましょう。
他人の褌で相撲を取るこの記事です。 1次ソースを基本的には御覧ください。 一番くわしいです。
Rustから呼び出すHTMLを作る
前回書いた記事と核となるところは変えてないですが。 add buttonを押されたら、/addとして用意したAPIを叩きます。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>App</title>
</head>
<body>
<div>
{% for entry in entries %}
<div>
<div>id: {{ entry.id }}, text: {{ entry.text }}</div>
<form action="/delete" method="post">
<input type="hidden" name="id" value="{{ entry.id }}">
<button>delete</button>
</form>
</div>
{% endfor %}
</div>
<form action="/add" method="post">
<div>
<input name="text">
</div>
<div>
<button>add</button>
</div>
</form>
</body>
</html>
次に、RustでAPIを用意していきます。
RustでSQLiteにアクセスするために
今回はAPIを叩いてSQLiteにアクセスするので、 SQLiteを操作するためにSQLライブラリを導入しましょう。
cargo add r2d2 r2d2_sqlite
データベースの初期化
Sqliteはローカルにデータベースを保持してデータを溜め込んで行くので その溜め込むデータベースを作りましょう。
let manager = SqliteConnectionManager::file("test.db");
let pool = Pool::new(manager).expect("Failed to initialize the connection pool");
let conn = pool.get().expect("Failed to initialize the connection pool.");
conn.execute(
"CREATE TABLE IF NOT EXISTS todo (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL
)",
params![],
).expect("Failed to create a table `todo`.");
thread ‘main’ panicked at ‘Failed to create a table todo
.: SqliteFailure(Error { code: Unknown, extended_code: 1 }, Some(“AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY”))’, src\main.rs:55:7
エラーがでて困ったさんのときは、SQL構文に誤りがあります。 このタイプのエラーはSQL式が間違えているときに出ます。 構文が間違えていないか見直すといいでしょう、
Compiling todolist v0.1.0 (E:\SourceCode\Rust\todolist)
Finished dev [unoptimized + debuginfo] target(s) in 49.89s
Running `target\debug\todolist.exe`
thread 'main' panicked at 'Failed to create a table `todo`.: SqliteFailure(Error { code: Unknown, extended_code: 1 }, Some("AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY"))', src\main.rs:55:7
stack backtrace:
0: 0x7ff73384b68e - std::backtrace_rs::backtrace::dbghelp::trace
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\..\..\backtrace\src\backtrace\dbghelp.rs:98
1: 0x7ff73384b68e - std::backtrace_rs::backtrace::trace_unsynchronized
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\..\..\backtrace\src\backtrace\mod.rs:66
2: 0x7ff73384b68e - std::sys_common::backtrace::_print_fmt
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:67
3: 0x7ff73384b68e - std::sys_common::backtrace::_print::impl$0::fmt
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:46
4: 0x7ff733860d6a - core::fmt::write
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\core\src\fmt\mod.rs:1150
5: 0x7ff733845c88 - std::io::Write::write_fmt<std::sys::windows::stdio::Stderr>
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\io\mod.rs:1667
6: 0x7ff73384e1f6 - std::sys_common::backtrace::_print
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:49
7: 0x7ff73384e1f6 - std::sys_common::backtrace::print
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\sys_common\backtrace.rs:36
8: 0x7ff73384e1f6 - std::panicking::default_hook::closure$1
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:210
9: 0x7ff73384dce4 - std::panicking::default_hook
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35\/library\std\src\panicking.rs:227
(略)
Error enumを作る
MyError enumの中にSQLiteのエラーを追加する。
enum MyError {
#[error("Failed to render HTML")]
AskamaError(#[from] askama::Error),
#[error("Failed to get connection")]
ConnectionPoolError(#[from] r2d2::Error),
#[error("Failed SQL execution")]
SQLiteError(#[from] rusqlite::Error),
}
SQLiteのデータを取り出す
ルートパスを叩くと、自動でSQLiteからデータを抜き出すようにします。
コネクタを作り、そのコネクタを使ってデータベースにアクセスします。
let conn = db.get()?;
データから抜き出すのはこう書きました。 抜き出したパラメータをrowsに詰め込んでいます。
let mut statement = conn.prepare("SELECT id, text FROM todo")?;
let rows = statement.query_map(params![], |row| {
let id = row.get(0)?;
let text = row.get(1)?;
Ok(TodoEntry { id, text })
})?;
あとは取り出す所まとめて、こう書きました。
#[get("/")]
async fn index(db: web::Data<Pool<SqliteConnectionManager>>) -> Result<HttpResponse, MyError> {
let conn = db.get()?;
let mut statement = conn.prepare("SELECT id, text FROM todo")?;
let rows = statement.query_map(params![], |row| {
let id = row.get(0)?;
let text = row.get(1)?;
Ok(TodoEntry { id, text })
})?;
let mut entries = Vec::new();
for row in rows {
entries.push(row?);
}
let html = IndexTemplate { entries };
let response_body = html.render()?;
Ok(HttpResponse::Ok()
.content_type("text/html")
.body(response_body))
}
SQLiteにデータを詰め込む
addが呼ばれたら、Inputタグで指定された文字列を取得して、 その文字列をINSERTでDBに詰め込みます。
#[post("/add")]
async fn add_todo(
params: web::Form<AddParams>,
db: web::Data<r2d2::Pool<SqliteConnectionManager>>,
) -> Result<HttpResponse, MyError> {
let conn = db.get()?;
conn.execute("INSERT INTO todo (text) VALUES (?)", &[¶ms.text])?;
Ok(HttpResponse::SeeOther()
.header(header::LOCATION, "/")
.finish())
}
SQLiteのデータを削除する
消すのはこうです。詰め込むのとやってるのは同じです。
#[post("/delete")]
async fn delete_todo(
params: web::Form<DeleteParams>,
db: web::Data<r2d2::Pool<SqliteConnectionManager>>,
) -> Result<HttpResponse, MyError> {
let conn = db.get()?;
conn.execute("DELETE FROM todo WHERE id=?", &[¶ms.id])?;
Ok(HttpResponse::SeeOther()
.header(header::LOCATION, "/")
.finish())
}
全文
Rust部分はこんな感じです。
use actix_web::{get, http::header, post, web, App, HttpResponse, HttpServer, ResponseError};
use askama::Template;
use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
use rusqlite::params;
use serde::Deserialize;
use thiserror::Error;
#[derive(Deserialize)]
struct AddParams {
text: String,
}
#[derive(Deserialize)]
struct DeleteParams {
id: u32,
}
struct TodoEntry {
id: u32,
text: String,
}
#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate {
entries: Vec<TodoEntry>,
}
#[derive(Error, Debug)]
enum MyError {
#[error("Failed to render HTML")]
AskamaError(#[from] askama::Error),
#[error("Failed to get connection")]
ConncectionPoolError(#[from] r2d2::Error),
#[error("Failed SQL execution")]
SQLiteError(#[from] rusqlite::Error),
}
impl ResponseError for MyError {}
#[post("/add")]
async fn add_todo(
params: web::Form<AddParams>,
db: web::Data<r2d2::Pool<SqliteConnectionManager>>,
) -> Result<HttpResponse, MyError> {
let conn = db.get()?;
conn.execute("INSERT INTO todo (text) VALUES (?)", &[¶ms.text])?;
Ok(HttpResponse::SeeOther()
.header(header::LOCATION, "/")
.finish())
}
#[post("/delete")]
async fn delete_todo(
params: web::Form<DeleteParams>,
db: web::Data<r2d2::Pool<SqliteConnectionManager>>,
) -> Result<HttpResponse, MyError> {
let conn = db.get()?;
conn.execute("DELETE FROM todo WHERE id=?", &[¶ms.id])?;
Ok(HttpResponse::SeeOther()
.header(header::LOCATION, "/")
.finish())
}
#[get("/")]
async fn index(db: web::Data<Pool<SqliteConnectionManager>>) -> Result<HttpResponse, MyError> {
let conn = db.get()?;
let mut statement = conn.prepare("SELECT id, text FROM todo")?;
let rows = statement.query_map(params![], |row| {
let id = row.get(0)?;
let text = row.get(1)?;
Ok(TodoEntry { id, text })
})?;
let mut entries = Vec::new();
for row in rows {
entries.push(row?);
}
let html = IndexTemplate { entries };
let response_body = html.render()?;
Ok(HttpResponse::Ok()
.content_type("text/html")
.body(response_body))
}
#[actix_web::main]
async fn main() -> Result<(), actix_web::Error> {
let manager = SqliteConnectionManager::file("todo.db");
let pool = Pool::new(manager).expect("Failed to initialize the connection pool.");
let conn = pool
.get()
.expect("Failed to get the connection from the pool.");
conn.execute(
"CREATE TABLE IF NOT EXISTS todo (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL
)",
params![],
)
.expect("Failed to create a table `todo`.");
HttpServer::new(move || {
App::new()
.service(index)
.service(add_todo)
.service(delete_todo)
.data(pool.clone())
})
.bind("127.0.0.1:1002")?
.run()
.await?;
Ok(())
}
結果出力
こんな感じです。
まとめ
これでRustからデータベース(SQLite)を使えるようになりましたね。 データは原油と言われるぐらい最近重要性をましてますからね。 まぁ原油も精油しないと使えないようにただあるだけだと、ゴミでいかにうまく加工できるかが大切らしいですね。
今日はこんな所で!!おわり!!!