Page MenuHomeDevCentral

D712.diff
No OneTemporary

D712.diff

diff --git a/Cargo.toml b/Cargo.toml
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,9 +11,14 @@
amqp = "0.0.19"
env_logger = "^0.3"
iron = "0.4.*"
+lazy_static = "*"
log = "^0.3.5"
+num_cpus = "0.2"
+persistent= "^0.2.1"
+r2d2 = "^0.7"
+r2d2_sqlite = "^0.1.0"
router = "*"
-rust-sqlite = "0.3.0"
+rusqlite = "^0.7.3"
[dev-dependencies]
iron-test = "0.4"
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -51,3 +51,11 @@
Note this is an executable, not a library.
As such, if you want to add a test, you can't use a tests/ folder:
the independent test crate won't be able to import the crate.
+
+## Acknowledgment
+
+Thanks to the following individuals:
+
+ * Vincent Prouillet aka Keats
+ * Matthieu Wipliez
+
diff --git a/src/http_server.rs b/src/http_server.rs
--- a/src/http_server.rs
+++ b/src/http_server.rs
@@ -6,7 +6,11 @@
License: BSD-2-Clause
------------------------------------------------------------- */
+use store::SqliteDB;
+use store::SqlitePool;
+
use iron::prelude::*;
+use persistent::Read;
use router::Router;
use std::env;
@@ -22,27 +26,34 @@
Controller logic is defined in the web_handlers module.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-/// Prepares an Iron handler.
-fn initialize_handler() -> Router {
+/// Prepares an Iron handler to route requests.
+fn get_router() -> Router {
router!(
index: get "/" => ::web_handlers::get,
status: get "/status" => ::web_handlers::alive
)
}
+/// Prepares an Iron handler.
+fn initialize_handler(pool: SqlitePool) -> Chain {
+ let mut chain = Chain::new(get_router());
+ chain.link(Read::<SqliteDB>::both(pool));
+
+ chain
+}
+
/// Gets the address the HTTP server should bind to.
fn get_bind_address() -> String {
env::var("BIND").unwrap_or("0.0.0.0:3000".to_string())
}
/// Runs an HTTP server.
-pub fn run() {
+pub fn run(pool: SqlitePool) {
let listen = get_bind_address();
- info!("Starting HTTP server");
info!("Listen to http://{}", listen);
- let _server = Iron::new(initialize_handler()).http(&*listen);
+ let _server = Iron::new(initialize_handler(pool)).http(&*listen);
if let Err(err) = _server {
error!("{}", err)
}
@@ -57,7 +68,7 @@
extern crate iron_test;
use super::get_bind_address;
- use super::initialize_handler;
+ use super::get_router;
use iron::Headers;
use iron::status;
@@ -73,7 +84,7 @@
fn status_returns_200_alive() {
let response = request::get("http://localhost:3000/status",
Headers::new(),
- &initialize_handler())
+ &get_router())
.expect("Can't get a request.");
assert_eq!(response.status, Some(status::Ok));
diff --git a/src/main.rs b/src/main.rs
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,21 +13,30 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#[macro_use]
+extern crate lazy_static;
+
+#[macro_use]
extern crate log;
extern crate env_logger;
+extern crate num_cpus;
+
+extern crate r2d2;
+extern crate r2d2_sqlite;
+
+extern crate rusqlite;
+
extern crate iron;
+extern crate persistent;
#[macro_use]
extern crate router;
-extern crate sqlite3;
-
/* -------------------------------------------------------------
Modules
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-mod http_server;
mod store;
+mod http_server;
mod web_handlers;
/* -------------------------------------------------------------
@@ -42,5 +51,9 @@
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"));
- http_server::run();
+ info!("Preparing SQLite pool");
+ let pool = ::store::initialize_pool();
+
+ info!("Starting HTTP server");
+ http_server::run(pool);
}
diff --git a/src/store.rs b/src/store.rs
--- a/src/store.rs
+++ b/src/store.rs
@@ -6,12 +6,15 @@
License: BSD-2-Clause
------------------------------------------------------------- */
-use sqlite3::DatabaseConnection;
-use sqlite3::SqliteResult;
-use sqlite3::StatementUpdate;
-use sqlite3::ToSql;
-use sqlite3::access::ByFilename;
+use iron::typemap::Key;
+use r2d2;
+use r2d2_sqlite::SqliteConnectionManager;
+
+use rusqlite::types::ToSql;
+use rusqlite::Error as DBError;
+
+use std::error::Error;
use std::env;
use std::fs;
use std::io;
@@ -29,99 +32,153 @@
}
/* -------------------------------------------------------------
+ SQLite pool
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+pub type SqlitePool = r2d2::Pool<SqliteConnectionManager>;
+pub type SqliteConnection = r2d2::PooledConnection<SqliteConnectionManager>;
+
+pub struct SqliteDB;
+impl Key for SqliteDB { type Value = SqlitePool;}
+
+/// Gets the size of the pool.
+fn get_pool_size() -> usize {
+ 8 * ::num_cpus::get() + 1
+}
+
+fn get_pool_of_size(size: u32) -> SqlitePool {
+ let config = r2d2::Config::builder().pool_size(size).build();
+ let manager = SqliteConnectionManager::new(&*get_path());
+
+ r2d2::Pool::new(config, manager).unwrap()
+}
+
+/// Gets a pool of SQLite connections.
+/// The pool size should match the number of thread of the HTTP server + 1 for the broker thread.
+pub fn get_pool() -> SqlitePool {
+ let size = get_pool_size() as u32;
+ get_pool_of_size(size)
+}
+
+/// Initializes a SQLite database and a pool of connections.
+pub fn initialize_pool() -> SqlitePool {
+ let pool = get_pool();
+
+ let connection = pool.get().unwrap();
+ let mut store = DataStore::new(connection);
+ store.init();
+
+ pool
+}
+
+/* -------------------------------------------------------------
Data store context
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
pub struct DataStore {
- /// The path to the SQLite database filename
- /// By default, honours $STORE environment variable, or if omitted, "./log.db".
- filename: String,
-
/// The connexion to the database
- connection: DatabaseConnection,
+ connection: SqliteConnection,
}
impl DataStore {
+
+ /* -------------------------------------------------------------
+ Constructor
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
/// Initializes a new instance of the DataStore object.
- pub fn new () -> Result<DataStore, String> {
- let path = ::store::get_path();
- match ::store::get_connection() {
- Ok(database_connection) => {
- let mut store = DataStore {
- filename: path,
- connection: database_connection
- };
- store.init();
- Ok(store)
- },
- Err(err) => Err(err.desc.to_string())
+ pub fn new (database_connection: SqliteConnection) -> DataStore {
+ DataStore {
+ connection: database_connection
}
}
+ /* -------------------------------------------------------------
+ Query helpers
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+ /// Executes a query.
+ fn execute (&self, query: &str, params: &[&ToSql]) -> Result<i32, String> {
+ debug!("SQL query: {}", query);
+
+ match self.connection.execute(query, params) {
+ Ok(rows_updated) => Ok(rows_updated),
+ Err(err) => {
+ let error = format!("{}", err.description());
+ error!("SQL error: {}", error);
+
+ Err(error)
+ }
+ }
+ }
+
+ /// Runs a SELECT query, then maps rows as LogEntry.
+ fn select_all (&self, query: &str) -> Result<Vec<LogEntry>, DBError> {
+ let mut statement = try!(self.connection.prepare(query));
+
+ let rows = try!(statement.query_map(&[], |row|
+ LogEntry {
+ date: row.get(0),
+ emitter: row.get(1),
+ source: row.get(2),
+ component: row.get(3),
+ entry: row.get(4),
+ }
+ ));
+
+ rows.collect()
+ }
+
+ /* -------------------------------------------------------------
+ CRUD methods
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
/// Initializes the data store.
pub fn init(&mut self) {
- self.connection.exec(::store::get_schema()).unwrap();
+ info!("Writing database schema");
+ self.connection.execute(::store::get_schema(), &[]).unwrap();
}
- /// Destroys the data store.
- pub fn destroy (&self) -> Result<(), io::Error> {
- try!(fs::remove_file(&*self.filename));
- info!("Destroyed {}", self.filename);
- Ok(())
- }
- /// Executes a prepared statement.
- /// - query: the SQL query, each parameter replaced by $1, $2, etc.
- /// - parameters: the parameters to put in the query
- pub fn exec_prepared_statement (&self, query: &str, parameters: &[&ToSql]) -> Result<u64, String> {
- match self.connection.prepare(query).unwrap().update(parameters) {
- Ok(rows_updated) => Ok(rows_updated),
- Err(err) => Err(err.desc.to_string()),
- }
+ /// Gets all the log entries.
+ pub fn select_all_log_entries (&self) -> Result<Vec<LogEntry>, DBError> {
+ self.select_all(
+ "SELECT date, emitter, source, component, entry FROM log"
+ )
}
/// Inserts an entry to the log.
- pub fn insert (&self, entry: LogEntry) -> Result<u64, String> {
+ pub fn insert (&self, entry: LogEntry) -> Result<i32, String> {
debug!("Inserting new log entry");
- self.exec_prepared_statement(
+ self.execute(
"INSERT INTO log (date, emitter, source, component, entry)
VALUES ($1, $2, $3, $4, $5);",
&[&entry.date, &entry.emitter, &entry.source, &entry.component, &entry.entry]
)
}
+
}
/* -------------------------------------------------------------
- Helper functions — Database
+ Helper functions
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-/// Opens a connection to the database.
-fn get_connection () -> SqliteResult<DatabaseConnection> {
- let path = &*get_path();
- info!("Opening database {}", path);
-
- DatabaseConnection::new(
- ByFilename {
- filename: path,
- flags: Default::default(),
- }
- )
-}
-
/// Gets the SQL tables schema for the log store.
pub fn get_schema<'a> () -> &'a str {
include_str!("../sql/schema.sql")
}
-/* -------------------------------------------------------------
- Helper functions
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
-
/// Gets the path to the store to use.
fn get_path() -> String {
env::var("STORE").unwrap_or("log.db".to_string())
}
+/// Destroys the data store
+fn destroy() -> io::Result<()> {
+ try!(fs::remove_file(get_path()));
+ Ok(())
+}
+
/* -------------------------------------------------------------
Tests
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
@@ -129,7 +186,9 @@
#[cfg(test)]
mod tests {
+ use super::destroy;
use super::get_path;
+ use super::get_pool_of_size;
use super::get_schema;
use super::DataStore;
use super::LogEntry;
@@ -151,25 +210,35 @@
}
//
- // SQLite file tests
+ // Helpers to interact with data store
//
+ /// Gets a DataStore
+ fn get_store () -> DataStore {
+ let connection = get_pool_of_size(2).get().unwrap();
+ DataStore::new(connection)
+ }
+
/// Determines if the store exists.
fn store_exists () -> bool {
Path::new(&get_path()).exists()
}
+ //
+ // SQLite file tests
+ //
+
#[test]
fn get_store_path_returns_expected_default_value() {
assert_eq!("log.db", get_path());
}
#[test]
- fn store_exists_when_initialized_but_not_after_destroy() {
- let store = DataStore::new().unwrap();
+ fn store_exists_when_initialized() {
+ get_store().init();
assert_eq!(true, store_exists(), "Store doesn't exist after initialization.");
- store.destroy().unwrap();
+ destroy().unwrap();
assert_eq!(false, store_exists(), "Store still exists after being destroyed.");
}
@@ -178,8 +247,10 @@
//
#[test]
- fn insert_adds_a_row() {
- let store = DataStore::new().unwrap();
+ fn we_can_insert_and_select_a_row() {
+ let mut store = get_store();
+ store.init();
+
let rows_updated = store.insert(
LogEntry {
date: String::from("2016-03-30T13:03:00Z"),
@@ -191,7 +262,10 @@
).unwrap();
assert_eq!(1, rows_updated);
- store.destroy().unwrap();
+ let entries = store.select_all_log_entries().unwrap();
+ assert_eq!(1, entries.len());
+
+ destroy().unwrap();
}
}
diff --git a/src/web_handlers.rs b/src/web_handlers.rs
--- a/src/web_handlers.rs
+++ b/src/web_handlers.rs
@@ -9,6 +9,30 @@
use iron::prelude::*;
use iron::status;
+use ::store::DataStore;
+
+/* -------------------------------------------------------------
+ Database
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/// Gets a data store, with a connection from the pool
+/// from the given request or returns a 500 error.
+macro_rules! get_store {
+ ($req:expr) => (match $req.get::<::persistent::Read<::store::SqliteDB>>() {
+ Ok(pool) => match pool.get() {
+ Ok(connection) => DataStore::new(connection),
+ Err(_) => {
+ error!("Can't get from the pool a database connection.");
+ return Ok(Response::with((status::InternalServerError)));
+ }
+ },
+ Err(_) => {
+ error!("Can't get from the pool a database connection.");
+ return Ok(Response::with((status::InternalServerError)));
+ }
+ })
+}
+
/* -------------------------------------------------------------
Handlers for web requests
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
@@ -20,6 +44,9 @@
/// Serves the log as a JSON document.
/// Serves a 503 reply as it's not implemented yet.
-pub fn get(_: &mut Request) -> IronResult<Response> {
+pub fn get(request: &mut Request) -> IronResult<Response> {
+ let store = get_store!(request);
+ let entries = store.select_all_log_entries();
+
Ok(Response::with((status::ServiceUnavailable, "Once upon a time, a log was baked.\n")))
}

File Metadata

Mime Type
text/plain
Expires
Fri, Nov 22, 14:51 (7 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2256275
Default Alt Text
D712.diff (14 KB)

Event Timeline