From a9c1676e24cbd4ff151896f10ac0a8fdf406c922 Mon Sep 17 00:00:00 2001 From: Ziyang Hu Date: Mon, 11 Jul 2022 19:45:55 +0800 Subject: [PATCH] tx triples --- Cargo.toml | 1 + src/bin/cozo_http.rs | 4 ++ src/data/id.rs | 38 +++++++++++++ src/data/keyword.rs | 7 ++- src/data/mod.rs | 2 +- src/data/tx.rs | 1 - src/data/tx_triple.rs | 122 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 171 insertions(+), 4 deletions(-) delete mode 100644 src/data/tx.rs create mode 100644 src/data/tx_triple.rs diff --git a/Cargo.toml b/Cargo.toml index 401698fe..3745c77d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ ordered-float = { version = "3.0", features = ["serde"] } actix-web = "4.1.0" clap = { version = "3.2.8", features = ["derive"] } itertools = "0.10.3" +actix-cors = "0.6.1" cozorocks = { path = "cozorocks" } [target.'cfg(not(target_env = "msvc"))'.dependencies] diff --git a/src/bin/cozo_http.rs b/src/bin/cozo_http.rs index 29fbd6c5..f30379db 100644 --- a/src/bin/cozo_http.rs +++ b/src/bin/cozo_http.rs @@ -1,3 +1,4 @@ +use actix_cors::Cors; use actix_web::{post, web, App, HttpResponse, HttpServer, Responder}; use clap::Parser; use cozo::{AttrTxItem, Db}; @@ -107,8 +108,11 @@ async fn main() -> std::io::Result<()> { eprintln!("Serving database at {}:{}", addr.0, addr.1); HttpServer::new(move || { + let cors = Cors::permissive(); + App::new() .app_data(app_state.clone()) + .wrap(cors) .service(query) .service(transact) .service(transact_attr) diff --git a/src/data/id.rs b/src/data/id.rs index 9ed50b9b..ceb7fc0a 100644 --- a/src/data/id.rs +++ b/src/data/id.rs @@ -27,6 +27,44 @@ impl Validity { } } +impl From for Validity { + fn from(i: i64) -> Self { + Validity(i) + } +} + +impl TryFrom<&str> for Validity { + type Error = anyhow::Error; + + fn try_from(value: &str) -> Result { + let dt = + DateTime::parse_from_rfc2822(value).or_else(|_| DateTime::parse_from_rfc3339(value))?; + let sysdt: SystemTime = dt.into(); + let timestamp = sysdt.duration_since(UNIX_EPOCH).unwrap().as_micros() as i64; + Ok(Self(timestamp)) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum IdError { + #[error("Cannot convert to validity: {0}")] + JsonValidityError(serde_json::Value), +} + +impl TryFrom<&serde_json::Value> for Validity { + type Error = anyhow::Error; + + fn try_from(value: &serde_json::Value) -> Result { + if let Some(v) = value.as_i64() { + return Ok(v.into()); + } + if let Some(s) = value.as_str() { + return s.try_into(); + } + Err(IdError::JsonValidityError(value.clone()).into()) + } +} + impl Debug for Validity { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let dt = Utc.timestamp(self.0 / 1_000_000, (self.0 % 1_000_000) as u32 * 1000); diff --git a/src/data/keyword.rs b/src/data/keyword.rs index 47357c4a..d349392e 100644 --- a/src/data/keyword.rs +++ b/src/data/keyword.rs @@ -1,7 +1,6 @@ -use lazy_static::lazy_static; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value; use smartstring::{LazyCompact, SmartString}; -use std::collections::BTreeSet; use std::fmt::{Debug, Display, Formatter}; use std::str::Utf8Error; @@ -15,6 +14,9 @@ pub enum KeywordError { #[error(transparent)] Utf8(#[from] Utf8Error), + + #[error("unexpected json {0}")] + UnexpectedJson(serde_json::Value), } #[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Deserialize, Serialize)] @@ -45,6 +47,7 @@ impl TryFrom<&str> for Keyword { type Error = KeywordError; fn try_from(value: &str) -> Result { let make_err = || KeywordError::InvalidKeyword(value.to_string()); + let value = value.strip_prefix(':').unwrap_or(value); let mut kw_iter = value.split('/'); let ns = kw_iter.next().ok_or_else(make_err)?; let ident = match kw_iter.next() { diff --git a/src/data/mod.rs b/src/data/mod.rs index 1c380600..357be16c 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -5,7 +5,7 @@ pub(crate) mod id; pub(crate) mod json; pub(crate) mod keyword; pub(crate) mod triple; -pub(crate) mod tx; pub(crate) mod value; pub(crate) mod tx_attr; +pub(crate) mod tx_triple; diff --git a/src/data/tx.rs b/src/data/tx.rs deleted file mode 100644 index 8b137891..00000000 --- a/src/data/tx.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/data/tx_triple.rs b/src/data/tx_triple.rs new file mode 100644 index 00000000..9e37c551 --- /dev/null +++ b/src/data/tx_triple.rs @@ -0,0 +1,122 @@ +use crate::data::id::{AttrId, EntityId, Validity}; +use crate::data::keyword::Keyword; +use crate::data::triple::StoreOp; +use crate::data::value::Value; +use crate::runtime::transact::SessionTx; +use anyhow::Result; +use serde_json::Map; + +pub(crate) struct Triple<'a> { + id: EntityId, + attr: AttrId, + value: Value<'a>, +} + +pub struct Quintuple<'a> { + triple: Triple<'a>, + op: StoreOp, + validity: Validity, +} + +#[derive(Debug, thiserror::Error)] +pub enum TxError { + #[error("Error decoding {0}: {1}")] + Decoding(serde_json::Value, String), + #[error("triple length error")] + TripleLength, + #[error("attribute not found: {0}")] + AttrNotFound(Keyword), +} + +impl SessionTx { + /// Requests are like these + /// ```json + /// {"tx": [...], "comment": "a comment", "since": timestamp} + /// ``` + /// each line in `tx` is `{"put: ...}`, `{"retract": ...}`, `{"erase": ...}` or `{"ensure": ...}` + /// these can also have a `from` field, overriding the timestamp + /// the dots can be triples + /// ```json + /// [12345, ":x/y", 12345] + /// ``` + /// triples with tempid + /// ```json + /// ["tempid1", ":x/y", 12345] + /// ``` + /// objects format + /// ```json + /// { + /// "_id": 12345, + /// "_tempid": "xyzwf", + /// "ns/fieldname": 111 + /// } + /// ``` + /// nesting is allowed for values of type `ref` and `component` + pub fn parse_tx_requests<'a>( + &mut self, + req: &'a serde_json::Value, + ) -> Result<(Vec>, String)> { + let map = req + .as_object() + .ok_or_else(|| TxError::Decoding(req.clone(), "expected object".to_string()))?; + let items = map + .get("tx") + .ok_or_else(|| TxError::Decoding(req.clone(), "expected field 'tx'".to_string()))? + .as_array() + .ok_or_else(|| { + TxError::Decoding( + req.clone(), + "expected field 'tx' to be an array".to_string(), + ) + })?; + let default_since = match map.get("since") { + None => Validity::current(), + Some(v) => v.try_into()?, + }; + let comment = match map.get("comment") { + None => "".to_string(), + Some(v) => v.to_string(), + }; + let mut collected = Vec::with_capacity(items.len()); + for item in items { + collected.push(self.parse_tx_request_item(item, default_since)?) + } + Ok((collected, comment)) + } + fn parse_tx_request_item<'a>( + &mut self, + item: &'a serde_json::Value, + default_since: Validity, + ) -> Result> { + if let Some(arr) = item.as_array() { + return self.parse_tx_request_arr(arr, default_since); + } + + if let Some(obj) = item.as_object() { + return self.parse_tx_request_obj(obj, default_since); + } + + Err(TxError::Decoding(item.clone(), "expected object or array".to_string()).into()) + } + fn parse_tx_request_arr<'a>( + &mut self, + item: &'a [serde_json::Value], + default_since: Validity, + ) -> Result> { + match item { + [eid, attr_kw, val] => { + let kw: Keyword = attr_kw.try_into()?; + let attr = self.attr_by_kw(&kw)?.ok_or(TxError::AttrNotFound(kw))?; + todo!() + } + vs => Err(TxError::TripleLength.into()), + } + } + fn parse_tx_request_obj<'a>( + &mut self, + item: &'a Map, + default_since: Validity, + ) -> Result> { + todo!() + } +}