mirror of
https://github.com/fluencelabs/fluid
synced 2025-04-25 14:52:19 +00:00
initial commit
This commit is contained in:
parent
f75b3b3aea
commit
8bc7ac36b2
@ -1,64 +0,0 @@
|
|||||||
use log;
|
|
||||||
|
|
||||||
use crate::errors::AppResult;
|
|
||||||
|
|
||||||
use crate::errors::err_msg;
|
|
||||||
use crate::ffi;
|
|
||||||
|
|
||||||
// Execute query on SQLite
|
|
||||||
pub fn query(query: String) -> AppResult<String> {
|
|
||||||
log::debug!("executing query: '{}'", query);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
// Convert query string to bytes
|
|
||||||
let query_bytes = query.as_bytes();
|
|
||||||
// Allocate memory for query in SQLite module
|
|
||||||
let query_ptr = ffi::allocate(query_bytes.len());
|
|
||||||
|
|
||||||
// Store query in SQLite's memory
|
|
||||||
for (i, byte) in query_bytes.iter().enumerate() {
|
|
||||||
let ptr = query_ptr + i as i32;
|
|
||||||
ffi::store(ptr, *byte);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the query, and get pointer to the result
|
|
||||||
let result_ptr = ffi::invoke(query_ptr, query_bytes.len());
|
|
||||||
|
|
||||||
// First 4 bytes at result_ptr location encode result size, read that first
|
|
||||||
let mut result_size: usize = 0;
|
|
||||||
for i in 0..3 {
|
|
||||||
let ptr = result_ptr + i as i32;
|
|
||||||
let b = ffi::load(ptr) as usize;
|
|
||||||
result_size = result_size + (b << (8 * i));
|
|
||||||
}
|
|
||||||
// Now we know exact size of the query execution result
|
|
||||||
|
|
||||||
// Read query execution result byte-by-byte
|
|
||||||
let mut result_bytes = vec![0; result_size as usize];
|
|
||||||
for i in 4..(result_size + 4) {
|
|
||||||
let ptr = result_ptr + i as i32;
|
|
||||||
let b = ffi::load(ptr);
|
|
||||||
result_bytes[i as usize - 4] = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deallocate query result
|
|
||||||
ffi::deallocate(result_ptr, result_size + 4);
|
|
||||||
|
|
||||||
// Decode query result to a utf8 string
|
|
||||||
let result_str = std::str::from_utf8(result_bytes.as_slice());
|
|
||||||
|
|
||||||
// Log if there's an error
|
|
||||||
if result_str.is_err() {
|
|
||||||
log::error!("unable to decode result from bytes: {:#x?}", result_bytes);
|
|
||||||
}
|
|
||||||
// Wrap error with a better message, and return Result
|
|
||||||
result_str
|
|
||||||
.map_err(|e| {
|
|
||||||
err_msg(&format!(
|
|
||||||
"unable to decode result from bytes {:#x?}: {}",
|
|
||||||
result_bytes, e
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
// Description of inter-module communication
|
|
||||||
//
|
|
||||||
// Allows fluid module to call methods from sqlite module
|
|
||||||
|
|
||||||
#[link(wasm_import_module = "sqlite")]
|
|
||||||
extern "C" {
|
|
||||||
// Allocate chunk of SQLite memory, and return a pointer to that memory
|
|
||||||
#[link_name = "sqlite_allocate"]
|
|
||||||
pub fn allocate(size: usize) -> i32;
|
|
||||||
|
|
||||||
// Deallocate chunk of memory after it's not used anymore
|
|
||||||
#[link_name = "sqlite_deallocate"]
|
|
||||||
pub fn deallocate(ptr: i32, size: usize);
|
|
||||||
|
|
||||||
// Put 1 byte at ptr location in SQLite memory
|
|
||||||
#[link_name = "sqlite_store"]
|
|
||||||
pub fn store(ptr: i32, byte: u8);
|
|
||||||
|
|
||||||
// Read 1 byte from ptr location of SQLite memory
|
|
||||||
#[link_name = "sqlite_load"]
|
|
||||||
pub fn load(ptr: i32) -> u8;
|
|
||||||
|
|
||||||
// Call SQLite's invocation handler with data specified by pointer and size
|
|
||||||
#[link_name = "sqlite_invoke"]
|
|
||||||
pub fn invoke(ptr: i32, size: usize) -> i32;
|
|
||||||
}
|
|
@ -1,37 +1,39 @@
|
|||||||
use fluence::sdk::*;
|
use fluence::sdk::*;
|
||||||
|
|
||||||
pub mod database;
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod ffi;
|
pub mod utils;
|
||||||
|
|
||||||
|
use crate::utils::sqlite_call_wrapper;
|
||||||
|
|
||||||
fn init() {
|
fn init() {
|
||||||
logger::WasmLogger::init_with_level(log::Level::Info).unwrap();
|
logger::WasmLogger::init_with_level(log::Level::Info).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[invocation_handler(init_fn = init)]
|
#[invocation_handler(init_fn = init, side_modules = sqlite)]
|
||||||
fn run(nickname: String) -> String {
|
fn run(nickname: String) -> String {
|
||||||
// Create table for messages storage
|
// Create table for messages storage
|
||||||
database::query("CREATE TABLE messages(message text, username text)".to_string())
|
sqlite_call_wrapper("CREATE TABLE messages(message text, username text)")
|
||||||
.expect("error on CREATE TABLE");
|
.expect("error on CREATE TABLE");
|
||||||
|
|
||||||
// Insert message 'Hello, username!' using `nickname` as author's username
|
// Insert message 'Hello, username!' using `nickname` as author's username
|
||||||
database::query(format!(
|
sqlite_call_wrapper(
|
||||||
|
format!(
|
||||||
r#"INSERT INTO messages VALUES("{}","{}")"#,
|
r#"INSERT INTO messages VALUES("{}","{}")"#,
|
||||||
"Hello, username!", nickname
|
"Hello, username!", nickname
|
||||||
))
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
.expect("error on INSERT INTO");
|
.expect("error on INSERT INTO");
|
||||||
|
|
||||||
// Get all messages
|
// Get all messages
|
||||||
let messages =
|
let messages = sqlite_call_wrapper("SELECT * FROM messages").expect("error on SELECT *");
|
||||||
database::query("SELECT * FROM messages".to_string()).expect("error on SELECT *");
|
|
||||||
log::info!("messages: {:?}", messages);
|
log::info!("messages: {:?}", messages);
|
||||||
|
|
||||||
// Get all messages as JSON via SQLite's JSON extension
|
// Get all messages as JSON via SQLite's JSON extension
|
||||||
database::query(
|
sqlite_call_wrapper(
|
||||||
"SELECT json_group_array(
|
"SELECT json_group_array(
|
||||||
json_object('message', message, 'username', username)
|
json_object('message', message, 'username', username)
|
||||||
) AS json_result FROM (SELECT * FROM messages)"
|
) AS json_result FROM (SELECT * FROM messages)",
|
||||||
.to_string(),
|
|
||||||
)
|
)
|
||||||
.expect("error on SELECT as json")
|
.expect("error on SELECT as json")
|
||||||
}
|
}
|
||||||
|
24
backend-rust/step2-database-only/src/utils.rs
Normal file
24
backend-rust/step2-database-only/src/utils.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use crate::errors::{err_msg, AppResult};
|
||||||
|
use crate::sqlite;
|
||||||
|
|
||||||
|
pub fn sqlite_call_wrapper(bytes: &str) -> AppResult<String> {
|
||||||
|
let response = sqlite::call(bytes.as_bytes());
|
||||||
|
|
||||||
|
// Decode query result to a utf8 string
|
||||||
|
let result_str = std::str::from_utf8(&response);
|
||||||
|
|
||||||
|
// Log if there's an error
|
||||||
|
if result_str.is_err() {
|
||||||
|
log::error!("unable to decode result from bytes: {:#x?}", bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap error with a better message, and return Result
|
||||||
|
result_str
|
||||||
|
.map_err(|e| {
|
||||||
|
err_msg(&format!(
|
||||||
|
"unable to decode result from bytes {:#x?}: {}",
|
||||||
|
bytes, e
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
}
|
@ -21,4 +21,4 @@ panic = "abort"
|
|||||||
serde = { version = "=1.0.88", features = ["derive"] }
|
serde = { version = "=1.0.88", features = ["derive"] }
|
||||||
serde_json = { version = "=1.0.38", features = ["raw_value"] }
|
serde_json = { version = "=1.0.38", features = ["raw_value"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
fluence = { version = "0.1.6", features = ["wasm_logger"] }
|
fluence = { version = "0.1.7", features = ["wasm_logger"] }
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
use log;
|
|
||||||
|
|
||||||
use crate::errors::err_msg;
|
|
||||||
use crate::errors::AppResult;
|
|
||||||
use crate::ffi;
|
|
||||||
|
|
||||||
// Execute query on SQLite
|
|
||||||
pub fn query(query: String) -> AppResult<String> {
|
|
||||||
log::debug!("executing query: '{}'", query);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
// Convert query string to bytes
|
|
||||||
let query_bytes = query.as_bytes();
|
|
||||||
// Allocate memory for query in SQLite module
|
|
||||||
let query_ptr = ffi::allocate(query_bytes.len());
|
|
||||||
|
|
||||||
// Store query in SQLite's memory
|
|
||||||
for (i, byte) in query_bytes.iter().enumerate() {
|
|
||||||
let ptr = query_ptr + i as i32;
|
|
||||||
ffi::store(ptr, *byte);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the query, and get pointer to the result
|
|
||||||
let result_ptr = ffi::invoke(query_ptr, query_bytes.len());
|
|
||||||
|
|
||||||
// First 4 bytes at result_ptr location encode result size, read that first
|
|
||||||
let mut result_size: usize = 0;
|
|
||||||
for i in 0..3 {
|
|
||||||
let ptr = result_ptr + i as i32;
|
|
||||||
let b = ffi::load(ptr) as usize;
|
|
||||||
result_size = result_size + (b << (8 * i));
|
|
||||||
}
|
|
||||||
// Now we know exact size of the query execution result
|
|
||||||
|
|
||||||
// Read query execution result byte-by-byte
|
|
||||||
let mut result_bytes = vec![0; result_size as usize];
|
|
||||||
for i in 4..(result_size + 4) {
|
|
||||||
let ptr = result_ptr + i as i32;
|
|
||||||
let b = ffi::load(ptr);
|
|
||||||
result_bytes[i as usize - 4] = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deallocate query result
|
|
||||||
ffi::deallocate(result_ptr, result_size + 4);
|
|
||||||
|
|
||||||
// Decode query result to a utf8 string
|
|
||||||
let result_str = std::str::from_utf8(result_bytes.as_slice());
|
|
||||||
|
|
||||||
// Log if there's an error
|
|
||||||
if result_str.is_err() {
|
|
||||||
log::error!("unable to decode result from bytes: {:#x?}", result_bytes);
|
|
||||||
}
|
|
||||||
// Wrap error with a better message, and return Result
|
|
||||||
result_str
|
|
||||||
.map_err(|e| {
|
|
||||||
err_msg(&format!(
|
|
||||||
"unable to decode result from bytes {:#x?}: {}",
|
|
||||||
result_bytes, e
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
// Description of inter-module communication
|
|
||||||
//
|
|
||||||
// Allows fluid module to call methods from sqlite module
|
|
||||||
|
|
||||||
#[link(wasm_import_module = "sqlite")]
|
|
||||||
extern "C" {
|
|
||||||
// Allocate chunk of SQLite memory, and return a pointer to that memory
|
|
||||||
#[link_name = "sqlite_allocate"]
|
|
||||||
pub fn allocate(size: usize) -> i32;
|
|
||||||
|
|
||||||
// Deallocate chunk of memory after it's not used anymore
|
|
||||||
#[link_name = "sqlite_deallocate"]
|
|
||||||
pub fn deallocate(ptr: i32, size: usize);
|
|
||||||
|
|
||||||
// Put 1 byte at ptr location in SQLite memory
|
|
||||||
#[link_name = "sqlite_store"]
|
|
||||||
pub fn store(ptr: i32, byte: u8);
|
|
||||||
|
|
||||||
// Read 1 byte from ptr location of SQLite memory
|
|
||||||
#[link_name = "sqlite_load"]
|
|
||||||
pub fn load(ptr: i32) -> u8;
|
|
||||||
|
|
||||||
// Call SQLite's invocation handler with data specified by pointer and size
|
|
||||||
#[link_name = "sqlite_invoke"]
|
|
||||||
pub fn invoke(ptr: i32, size: usize) -> i32;
|
|
||||||
}
|
|
@ -8,17 +8,16 @@ use crate::errors::err_msg;
|
|||||||
use crate::errors::AppResult;
|
use crate::errors::AppResult;
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod database;
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod ffi;
|
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
fn init() {
|
fn init() {
|
||||||
logger::WasmLogger::init_with_level(log::Level::Info).unwrap();
|
logger::WasmLogger::init_with_level(log::Level::Info).unwrap();
|
||||||
model::create_scheme().unwrap();
|
model::create_scheme().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[invocation_handler(init_fn = init)]
|
#[invocation_handler(init_fn = init, side_modules = sqlite)]
|
||||||
fn run(arg: String) -> String {
|
fn run(arg: String) -> String {
|
||||||
// Parse and username JSON request
|
// Parse and username JSON request
|
||||||
let result = api::parse(arg).and_then(|request| match request {
|
let result = api::parse(arg).and_then(|request| match request {
|
||||||
|
@ -1,52 +1,46 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::database;
|
|
||||||
use crate::errors::err_msg;
|
use crate::errors::err_msg;
|
||||||
use crate::errors::AppResult;
|
use crate::errors::AppResult;
|
||||||
|
use crate::utils::sqlite_call_wrapper;
|
||||||
|
|
||||||
pub fn create_scheme() -> AppResult<()> {
|
pub fn create_scheme() -> AppResult<()> {
|
||||||
database::query("CREATE TABLE messages(message text, username text)".to_string())
|
sqlite_call_wrapper("CREATE TABLE messages(message text, username text)").map(drop)
|
||||||
.map_err(|e| err_msg(&format!("Error creating table messages: {}", e)))
|
|
||||||
.map(drop)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_post(message: String, username: String) -> AppResult<()> {
|
pub fn add_post(message: String, username: String) -> AppResult<()> {
|
||||||
database::query(format!(
|
sqlite_call_wrapper(
|
||||||
|
format!(
|
||||||
r#"INSERT INTO messages VALUES("{}","{}")"#,
|
r#"INSERT INTO messages VALUES("{}","{}")"#,
|
||||||
message, username
|
message, username
|
||||||
))
|
)
|
||||||
.map_err(|e| {
|
.as_str(),
|
||||||
err_msg(&format!(
|
)
|
||||||
"Error inserting post {} by {}: {}",
|
|
||||||
message, username, e
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.map(drop)
|
.map(drop)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all_posts() -> AppResult<String> {
|
pub fn get_all_posts() -> AppResult<String> {
|
||||||
database::query(
|
sqlite_call_wrapper(
|
||||||
"SELECT json_group_array(
|
"SELECT json_group_array(
|
||||||
json_object('message', message, 'username', username)
|
json_object('message', message, 'username', username)
|
||||||
) AS json_result FROM (SELECT * FROM messages)"
|
) AS json_result FROM (SELECT * FROM messages)",
|
||||||
.to_string(),
|
|
||||||
)
|
)
|
||||||
.map_err(|e| err_msg(&format!("Error retrieving posts: {}", e)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_posts_by_username(username: String) -> AppResult<String> {
|
pub fn get_posts_by_username(username: String) -> AppResult<String> {
|
||||||
database::query(format!(
|
sqlite_call_wrapper(
|
||||||
|
format!(
|
||||||
"SELECT json_group_array(
|
"SELECT json_group_array(
|
||||||
json_object('message', message, 'username', username)
|
json_object('message', message, 'username', username)
|
||||||
) AS json_result FROM (SELECT * FROM messages where username = '{}')",
|
) AS json_result FROM (SELECT * FROM messages where username = '{}')",
|
||||||
username
|
username
|
||||||
))
|
)
|
||||||
.map_err(|e| err_msg(&format!("Error retrieving posts: {}", e)))
|
.as_str(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_posts_count() -> AppResult<i32> {
|
pub fn get_posts_count() -> AppResult<i32> {
|
||||||
let result = database::query("SELECT COUNT(*) from messages".to_string())
|
let result = sqlite_call_wrapper("SELECT COUNT(*) from messages")?;
|
||||||
.map_err(|e| err_msg(&format!("Error retrieving posts count: {}", e)))?;
|
|
||||||
|
|
||||||
i32::from_str(result.as_str()).map_err(|e| {
|
i32::from_str(result.as_str()).map_err(|e| {
|
||||||
err_msg(&format!(
|
err_msg(&format!(
|
||||||
|
24
backend-rust/step3-finished-app/src/utils.rs
Normal file
24
backend-rust/step3-finished-app/src/utils.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use crate::errors::{err_msg, AppResult};
|
||||||
|
use crate::sqlite;
|
||||||
|
|
||||||
|
pub fn sqlite_call_wrapper(bytes: &str) -> AppResult<String> {
|
||||||
|
let response = sqlite::call(bytes.as_bytes());
|
||||||
|
|
||||||
|
// Decode query result to a utf8 string
|
||||||
|
let result_str = std::str::from_utf8(&response);
|
||||||
|
|
||||||
|
// Log if there's an error
|
||||||
|
if result_str.is_err() {
|
||||||
|
log::error!("unable to decode result from bytes: {:#x?}", bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap error with a better message, and return Result
|
||||||
|
result_str
|
||||||
|
.map_err(|e| {
|
||||||
|
err_msg(&format!(
|
||||||
|
"unable to decode result from bytes {:#x?}: {}",
|
||||||
|
bytes, e
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user