main
Ziyang Hu 2 years ago
parent 38a35b2a3c
commit 1c0c480b5e

42
Cargo.lock generated

@ -230,6 +230,25 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "cbindgen"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6358dedf60f4d9b8db43ad187391afe959746101346fe51bb978126bec61dfb"
dependencies = [
"clap",
"heck",
"indexmap",
"log",
"proc-macro2",
"quote",
"serde",
"serde_json",
"syn",
"tempfile",
"toml",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.74" version = "1.0.74"
@ -367,7 +386,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]] [[package]]
name = "cozo" name = "cozo"
version = "0.1.2" version = "0.1.3"
dependencies = [ dependencies = [
"approx", "approx",
"base64", "base64",
@ -412,23 +431,20 @@ dependencies = [
[[package]] [[package]]
name = "cozo_c" name = "cozo_c"
version = "0.1.1" version = "0.1.3"
dependencies = [ dependencies = [
"cbindgen",
"cozo", "cozo",
"lazy_static", "lazy_static",
"miette",
"serde_json",
] ]
[[package]] [[package]]
name = "cozo_java" name = "cozo_java"
version = "0.1.1" version = "0.1.3"
dependencies = [ dependencies = [
"cozo", "cozo",
"lazy_static", "lazy_static",
"miette",
"robusta_jni", "robusta_jni",
"serde_json",
] ]
[[package]] [[package]]
@ -1684,6 +1700,9 @@ name = "serde"
version = "1.0.147" version = "1.0.147"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
dependencies = [
"serde_derive",
]
[[package]] [[package]]
name = "serde_bytes" name = "serde_bytes"
@ -2002,6 +2021,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "twoway" name = "twoway"
version = "0.1.8" version = "0.1.8"

@ -1,6 +1,6 @@
[package] [package]
name = "cozo" name = "cozo"
version = "0.1.2" version = "0.1.3"
edition = "2021" edition = "2021"
description = "A general-purpose, transactional, relational database that uses Datalog and focuses on graph data and algorithms" description = "A general-purpose, transactional, relational database that uses Datalog and focuses on graph data and algorithms"
authors = ["Ziyang Hu"] authors = ["Ziyang Hu"]

@ -1,6 +1,6 @@
[package] [package]
name = "cozo_c" name = "cozo_c"
version = "0.1.1" version = "0.1.3"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
homepage = "https://github.com/cozodb/cozo" homepage = "https://github.com/cozodb/cozo"
@ -19,6 +19,7 @@ io-uring = ["cozo/io-uring"]
[dependencies] [dependencies]
cozo = { version = "0.1.2", path = ".." } cozo = { version = "0.1.2", path = ".." }
miette = { version = "=5.3.0", features = ["fancy"] }
serde_json = "1.0.81"
lazy_static = "1.4.0" lazy_static = "1.4.0"
[build-dependencies]
cbindgen = "0.24.3"

@ -0,0 +1,27 @@
/*
* Copyright 2022, The Cozo Project Authors. Licensed under MIT/Apache-2.0/BSD-3-Clause.
*/
use std::env;
use cbindgen::{Config, Language};
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let mut config = Config::default();
config.cpp_compat = true;
cbindgen::Builder::new()
.with_config(config)
.with_crate(crate_dir)
.with_language(Language::C)
.with_include_guard("COZO_C_H")
.with_autogen_warning(
"/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */",
)
.with_header("/* Copyright 2022, The Cozo Project Authors. Licensed under MIT/Apache-2.0/BSD-3-Clause. */")
.with_documentation(true)
.generate()
.expect("Unable to generate bindings")
.write_to_file("cozo_c.h");
}

@ -1,6 +0,0 @@
language = "C"
include_guard = "cozo_c_h"
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
cpp_compat = true
documentation = true
header = "/* Copyright 2022, The Cozo Project Authors. Licensed under MIT/Apache-2.0/BSD-3-Clause. */"

@ -1,7 +1,7 @@
/* Copyright 2022, The Cozo Project Authors. Licensed under MIT/Apache-2.0/BSD-3-Clause. */ /* Copyright 2022, The Cozo Project Authors. Licensed under MIT/Apache-2.0/BSD-3-Clause. */
#ifndef cozo_c_h #ifndef COZO_C_H
#define cozo_c_h #define COZO_C_H
/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ /* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
@ -49,10 +49,9 @@ bool cozo_close_db(int32_t id);
* `true` if an error occurred. * `true` if an error occurred.
* *
* Returns a UTF-8-encoded C-string that **must** be freed with `cozo_free_str`. * Returns a UTF-8-encoded C-string that **must** be freed with `cozo_free_str`.
* If `*errored` is false, then the string contains the JSON return value of the query. * The string contains the JSON return value of the query.
* If `*errored` is true, then the string contains the error message.
*/ */
char *cozo_run_query(int32_t db_id, const char *script_raw, const char *params_raw, bool *errored); char *cozo_run_query(int32_t db_id, const char *script_raw, const char *params_raw);
/** /**
* Free any C-string returned from the Cozo C API. * Free any C-string returned from the Cozo C API.
@ -66,4 +65,4 @@ void cozo_free_str(char *s);
} // extern "C" } // extern "C"
#endif // __cplusplus #endif // __cplusplus
#endif /* cozo_c_h */ #endif /* COZO_C_H */

@ -3,23 +3,17 @@
#include <stdbool.h> #include <stdbool.h>
#include "cozo_c.h" #include "cozo_c.h"
void run_query(int32_t db_id, const char* query) { void run_query(int32_t db_id, const char *query) {
const char *empty_params = "{}"; const char *empty_params = "{}";
bool errored;
char *res; char *res;
res = cozo_run_query(db_id, query, empty_params, &errored); res = cozo_run_query(db_id, query, empty_params);
printf("%s\n", res);
if (errored) {
printf("encountered an error:\n%s\n\n", res);
} else {
printf("query is successful with result:\n%s\n\n", res);
}
cozo_free_str(res); cozo_free_str(res);
} }
int main() { int main() {
int32_t db_id; int32_t db_id;
char* err = cozo_open_db("_test_db", &db_id); char *err = cozo_open_db("_test_db", &db_id);
if (err) { if (err) {
printf("%s", err); printf("%s", err);
@ -27,8 +21,7 @@ int main() {
return -1; return -1;
} }
run_query(db_id, "?[a, b, c] <- [[1, 2, 3]]"); run_query(db_id, "?[] <- [[1, 2, 3]]");
run_query(db_id, "?[a] <- [[1, 2, 3]]");
cozo_close_db(db_id); cozo_close_db(db_id);

@ -77,20 +77,19 @@ pub unsafe extern "C" fn cozo_close_db(id: i32) -> bool {
/// `true` if an error occurred. /// `true` if an error occurred.
/// ///
/// Returns a UTF-8-encoded C-string that **must** be freed with `cozo_free_str`. /// Returns a UTF-8-encoded C-string that **must** be freed with `cozo_free_str`.
/// If `*errored` is false, then the string contains the JSON return value of the query. /// The string contains the JSON return value of the query.
/// If `*errored` is true, then the string contains the error message.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn cozo_run_query( pub unsafe extern "C" fn cozo_run_query(
db_id: i32, db_id: i32,
script_raw: *const c_char, script_raw: *const c_char,
params_raw: *const c_char, params_raw: *const c_char,
errored: &mut bool,
) -> *mut c_char { ) -> *mut c_char {
let script = match CStr::from_ptr(script_raw).to_str() { let script = match CStr::from_ptr(script_raw).to_str() {
Ok(p) => p, Ok(p) => p,
Err(err) => { Err(_) => {
*errored = true; return CString::new(r##"{"ok":false,"message":"script is not UTF-8 encoded"}"##)
return CString::new(format!("{}", err)).unwrap().into_raw(); .unwrap()
.into_raw();
} }
}; };
let db = { let db = {
@ -100,52 +99,26 @@ pub unsafe extern "C" fn cozo_run_query(
}; };
match db_ref { match db_ref {
None => { None => {
*errored = true; return CString::new(r##"{"ok":false,"message":"database closed"}"##)
return CString::new("database already closed").unwrap().into_raw(); .unwrap()
.into_raw();
} }
Some(db) => db, Some(db) => db,
} }
}; };
let params_str = match CStr::from_ptr(params_raw).to_str() { let params_str = match CStr::from_ptr(params_raw).to_str() {
Ok(p) => p, Ok(p) => p,
Err(err) => {
*errored = true;
return CString::new(format!("{}", err)).unwrap().into_raw();
}
};
let params_map: serde_json::Value = match serde_json::from_str(&params_str) {
Ok(m) => m,
Err(_) => { Err(_) => {
*errored = true; return CString::new(
return CString::new("the given params argument is not valid JSON") r##"{"ok":false,"message":"params argument is not UTF-8 encoded"}"##,
.unwrap() )
.into_raw(); .unwrap()
.into_raw();
} }
}; };
let params_arg: BTreeMap<_, _> = match params_map { let result = db.run_script_str(script, &params_str);
serde_json::Value::Object(m) => m.into_iter().collect(), CString::new(result).unwrap().into_raw()
_ => {
*errored = true;
return CString::new("the given params argument is not a JSON map")
.unwrap()
.into_raw();
}
};
let result = db.run_script(script, &params_arg);
match result {
Ok(json) => {
*errored = false;
let json_str = json.to_string();
CString::new(json_str).unwrap().into_raw()
}
Err(err) => {
let err_str = format!("{:?}", err);
*errored = true;
CString::new(err_str).unwrap().into_raw()
}
}
} }
/// Free any C-string returned from the Cozo C API. /// Free any C-string returned from the Cozo C API.

@ -1,6 +1,6 @@
[package] [package]
name = "cozo_java" name = "cozo_java"
version = "0.1.1" version = "0.1.3"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
homepage = "https://github.com/cozodb/cozo" homepage = "https://github.com/cozodb/cozo"
@ -19,7 +19,5 @@ io-uring = ["cozo/io-uring"]
[dependencies] [dependencies]
robusta_jni = "0.2.0" robusta_jni = "0.2.0"
cozo = { version = "0.1.2", path = ".." } cozo = { version = "0.1.3", path = ".." }
miette = { version = "=5.3.0", features = ["fancy"] }
serde_json = "1.0.81"
lazy_static = "1.4.0" lazy_static = "1.4.0"

@ -22,7 +22,6 @@ lazy_static! {
#[bridge] #[bridge]
mod jni { mod jni {
use std::collections::BTreeMap;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use robusta_jni::convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}; use robusta_jni::convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue};
@ -73,22 +72,7 @@ mod jni {
let db = db_ref.ok_or_else(|| JniError::from("database already closed"))?; let db = db_ref.ok_or_else(|| JniError::from("database already closed"))?;
db db
}; };
let params_map: serde_json::Value = serde_json::from_str(&params_str) Ok(db.run_script_str(&script, &params_str))
.map_err(|_| JniError::from("the given params argument is not valid JSON"))?;
let params_arg: BTreeMap<_, _> = match params_map {
serde_json::Value::Object(m) => m.into_iter().collect(),
_ => {
return Err(JniError::from(
"the given params argument is not a JSON map",
))
}
};
let result = db.run_script(&script, &params_arg);
match result {
Ok(json) => Ok(json.to_string()),
Err(err) => Err(JniError::from(format!("{:?}", err))),
}
} }
} }
} }

@ -2,20 +2,17 @@
* Copyright 2022, The Cozo Project Authors. Licensed under AGPL-3 or later. * Copyright 2022, The Cozo Project Authors. Licensed under AGPL-3 or later.
*/ */
use std::collections::BTreeMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::fs; use std::fs;
use std::net::Ipv6Addr; use std::net::Ipv6Addr;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::time::Instant;
use clap::Parser; use clap::Parser;
use env_logger::Env; use env_logger::Env;
use log::{error, info}; use log::{error, info};
use rand::Rng; use rand::Rng;
use rouille::{router, try_or_400, Request, Response}; use rouille::{router, try_or_400, Request, Response};
use serde_json::json;
use cozo::Db; use cozo::Db;
@ -96,24 +93,30 @@ fn main() {
#[derive(serde_derive::Serialize, serde_derive::Deserialize)] #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
struct QueryPayload { struct QueryPayload {
script: String, script: String,
params: BTreeMap<String, serde_json::Value>, params: serde_json::Map<String, serde_json::Value>,
} }
let payload: QueryPayload = try_or_400!(rouille::input::json_input(request)); let payload: QueryPayload = try_or_400!(rouille::input::json_input(request));
let start = Instant::now(); let result = db.run_script_fold_err(&payload.script, &payload.params);
let response = Response::json(&result);
match db.run_script(&payload.script, &payload.params) { if let Some(serde_json::Value::Bool(true)) = result.get("ok") {
Ok(mut result) => { response
if let Some(obj) = result.as_object_mut() { } else {
obj.insert( response.with_status_code(400)
"time_taken".to_string(),
json!(start.elapsed().as_millis() as u64),
);
}
Response::json(&result)
}
Err(e) => Response::text(format!("{:?}", e)).with_status_code(400),
} }
// {
//
// Ok(mut result) => {
// if let Some(obj) = result.as_object_mut() {
// obj.insert(
// "time_taken".to_string(),
// json!(start.elapsed().as_millis() as u64),
// );
// }
// Response::json(&result)
// }
// _ => Response::json(&result).with_status_code(400)
// }
}, },
(GET) (/) => { (GET) (/) => {
Response::html(HTML_CONTENT) Response::html(HTML_CONTENT)
@ -163,7 +166,7 @@ const HTML_CONTENT: &str = r##"
})) }))
} }
} else { } else {
console.error(await resp.text()) console.error((await resp.json()).display)
} }
} }
console.log( console.log(

@ -7,13 +7,17 @@ use std::fmt::{Debug, Formatter};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use std::{fs, thread}; use std::{fs, thread};
use either::{Left, Right}; use either::{Left, Right};
use itertools::Itertools; use itertools::Itertools;
use miette::{bail, ensure, miette, Diagnostic, IntoDiagnostic, Result, WrapErr}; use lazy_static::lazy_static;
use serde_json::json; use miette::{
bail, ensure, miette, Diagnostic, GraphicalReportHandler, IntoDiagnostic, JSONReportHandler,
Result, WrapErr,
};
use serde_json::{json, Map};
use smartstring::SmartString; use smartstring::SmartString;
use thiserror::Error; use thiserror::Error;
@ -79,6 +83,11 @@ impl Debug for Db {
#[diagnostic(code(db::init))] #[diagnostic(code(db::init))]
struct BadDbInit(#[help] String); struct BadDbInit(#[help] String);
lazy_static! {
static ref TEXT_ERR_HANDLER: GraphicalReportHandler = miette::GraphicalReportHandler::new();
static ref JSON_ERR_HANDLER: JSONReportHandler = miette::JSONReportHandler::new();
}
impl Db { impl Db {
/// Creates a database object. /// Creates a database object.
pub fn new(path: impl AsRef<str>) -> Result<Self> { pub fn new(path: impl AsRef<str>) -> Result<Self> {
@ -175,24 +184,65 @@ impl Db {
Ok(ret) Ok(ret)
} }
/// Run the CozoScript passed in. The `params` argument is a map of parameters. /// Run the CozoScript passed in. The `params` argument is a map of parameters.
pub fn run_script( pub fn run_script(&self, payload: &str, params: &Map<String, JsonValue>) -> Result<JsonValue> {
&self, let start = Instant::now();
payload: &str, match self.do_run_script(payload, params) {
params: &BTreeMap<String, JsonValue>, Ok(mut json) => {
) -> Result<JsonValue> { let took = start.elapsed().as_secs_f64();
self.do_run_script(payload, params).map_err(|err| { let map = json.as_object_mut().unwrap();
if err.source_code().is_some() { map.insert("ok".to_string(), json!(true));
err map.insert("took".to_string(), json!(took));
} else { Ok(json)
err.with_source_code(payload.to_string())
} }
}) err => err,
}
} }
fn do_run_script( /// Run the CozoScript passed in. The `params` argument is a map of parameters.
&self, /// Fold any error into the return JSON itself.
payload: &str, pub fn run_script_fold_err(&self, payload: &str, params: &Map<String, JsonValue>) -> JsonValue {
params: &BTreeMap<String, JsonValue>, match self.run_script(payload, params) {
) -> Result<JsonValue> { Ok(json) => json,
Err(mut err) => {
if err.source_code().is_none() {
err = err.with_source_code(payload.to_string());
}
let mut text_err = String::new();
let mut json_err = String::new();
TEXT_ERR_HANDLER
.render_report(&mut text_err, err.as_ref())
.expect("render text error failed");
JSON_ERR_HANDLER
.render_report(&mut json_err, err.as_ref())
.expect("render json error failed");
let mut json: serde_json::Value =
serde_json::from_str(&json_err).expect("parse rendered json error failed");
let map = json.as_object_mut().unwrap();
map.insert("ok".to_string(), json!(false));
map.insert("display".to_string(), json!(text_err));
json
}
}
}
/// Run the CozoScript passed in. The `params` argument is a map of parameters formatted as JSON.
pub fn run_script_str(&self, payload: &str, params: &str) -> String {
let params_json = if params.is_empty() {
Map::default()
} else {
match serde_json::from_str::<serde_json::Value>(params) {
Ok(serde_json::Value::Object(map)) => map,
Ok(_) => {
return json!({"ok": false, "message": "params argument is not valid JSON"})
.to_string()
}
Err(_) => {
return json!({"ok": false, "message": "params argument is not a JSON map"})
.to_string()
}
}
};
self.run_script_fold_err(payload, &params_json).to_string()
}
fn do_run_script(&self, payload: &str, params: &Map<String, JsonValue>) -> Result<JsonValue> {
let param_pool = params let param_pool = params
.iter() .iter()
.map(|(k, v)| (k.clone(), DataValue::from(v))) .map(|(k, v)| (k.clone(), DataValue::from(v)))

Loading…
Cancel
Save