diff --git a/Cargo.lock b/Cargo.lock index ee47e594..f783e5ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,7 +113,7 @@ dependencies = [ [[package]] name = "butane" -version = "0.3.1" +version = "0.4.0" dependencies = [ "butane_codegen", "butane_core", @@ -128,12 +128,13 @@ dependencies = [ "quote 1.0.9", "r2d2", "rusqlite", + "serde_json", "uuid", ] [[package]] name = "butane_cli" -version = "0.3.1" +version = "0.4.0" dependencies = [ "anyhow", "butane", @@ -146,7 +147,7 @@ dependencies = [ [[package]] name = "butane_codegen" -version = "0.3.1" +version = "0.4.0" dependencies = [ "butane_core", "proc-macro2 1.0.26", @@ -157,7 +158,7 @@ dependencies = [ [[package]] name = "butane_core" -version = "0.3.1" +version = "0.4.0" dependencies = [ "bytes", "cfg-if", diff --git a/butane/Cargo.toml b/butane/Cargo.toml index 9f6c138b..fbcdfa9a 100644 --- a/butane/Cargo.toml +++ b/butane/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "butane" -version = "0.3.1" +version = "0.4.0" authors = ["James Oakley "] edition = "2018" description = "An ORM with a focus on simplicity and on writing Rust, not SQL." @@ -23,8 +23,8 @@ tls = ["butane_core/tls"] uuid = ["butane_core/uuid", "butane_codegen/uuid"] [dependencies] -butane_codegen = { path = "../butane_codegen", version = "0.3.1" } -butane_core = { path = "../butane_core", version = "0.3.1" } +butane_codegen = { path = "../butane_codegen", version = "0.4" } +butane_core = { path = "../butane_core", version = "0.4" } [dev-dependencies] @@ -39,6 +39,7 @@ once_cell="1.5.2" postgres = { version = "0.19", features=["with-geo-types-0_7"] } r2d2_for_test = {package="r2d2", version = "0.8"} rusqlite = "0.25" +serde_json = "1.0" uuid_for_test = {package="uuid", version = "0.8", features=["v4"] } [package.metadata.docs.rs] diff --git a/butane/tests/query.rs b/butane/tests/query.rs index 95f6e91d..7721daf6 100644 --- a/butane/tests/query.rs +++ b/butane/tests/query.rs @@ -1,9 +1,10 @@ use butane::db::Connection; use butane::prelude::*; use butane::query::BoolExpr; -use butane::{colname, filter, find, query}; +use butane::{colname, filter, find, query, Many}; use chrono::{TimeZone, Utc}; use paste; +use serde_json; mod common; use common::blog; @@ -130,6 +131,19 @@ fn many_load(conn: Connection) { } testall!(many_load); +fn many_serialize(conn: Connection) { + blog::setup_blog(&conn); + let post: Post = find!(Post, title == "The Tiger", &conn).unwrap(); + let tags_json: String = serde_json::to_string(&post.tags).unwrap(); + let tags: Many = serde_json::from_str(&tags_json).unwrap(); + let tags = tags.load(&conn).unwrap(); + let mut tags: Vec<&Tag> = tags.collect(); + tags.sort_by(|t1, t2| (*t1).tag.partial_cmp(&t2.tag).unwrap()); + assert_eq!(tags[0].tag, "asia"); + assert_eq!(tags[1].tag, "danger"); +} +testall!(many_serialize); + fn many_objects_with_tag(conn: Connection) { blog::setup_blog(&conn); let mut posts = query!(Post, tags.contains("danger")).load(&conn).unwrap(); diff --git a/butane_cli/Cargo.toml b/butane_cli/Cargo.toml index 1bd86df4..6de67cb4 100644 --- a/butane_cli/Cargo.toml +++ b/butane_cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "butane_cli" -version = "0.3.1" +version = "0.4.0" authors = ["James Oakley "] edition = "2018" description = "The CLI for the Butane ORM" @@ -15,7 +15,7 @@ path = "src/main.rs" [dependencies] anyhow = "1.0" -butane = { path="../butane", version="0.3.1", features=["default", "sqlite", "pg"] } +butane = { path="../butane", version="0.4", features=["default", "sqlite", "pg"] } chrono = "0.4" clap = "2.33" quote = "1.0" diff --git a/butane_codegen/Cargo.toml b/butane_codegen/Cargo.toml index a812f2d4..d08e0e70 100644 --- a/butane_codegen/Cargo.toml +++ b/butane_codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "butane_codegen" -version = "0.3.1" +version = "0.4.0" authors = ["James Oakley "] edition = "2018" description = "Macros for Butane. Do not use this crate directly -- use the butane crate." @@ -13,7 +13,7 @@ datetime = [] [dependencies] proc-macro2 = "1.0" -butane_core = { path = "../butane_core", version = "0.3.1" } +butane_core = { path = "../butane_core", version = "0.4" } quote = "1.0" syn = { version = "1.0", features = ["full", "extra-traits"] } uuid = {version = "0.8", optional=true} diff --git a/butane_core/Cargo.toml b/butane_core/Cargo.toml index e890216d..c71815d8 100644 --- a/butane_core/Cargo.toml +++ b/butane_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "butane_core" -version = "0.3.1" +version = "0.4.0" authors = ["James Oakley "] edition = "2018" description = "Internals for Butane. Do not use this crate directly -- use the butane crate." diff --git a/butane_core/src/many.rs b/butane_core/src/many.rs index 5c3ffd00..7745647a 100644 --- a/butane_core/src/many.rs +++ b/butane_core/src/many.rs @@ -2,6 +2,12 @@ use crate::db::{Column, ConnectionMethods}; use crate::query::{BoolExpr, Expr}; use crate::{DataObject, Error, FieldType, Result, SqlType, SqlVal, ToSql}; use once_cell::unsync::OnceCell; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; + +fn default_oc() -> OnceCell> { + OnceCell::default() +} /// Used to implement a many-to-many relationship between models. /// @@ -10,15 +16,18 @@ use once_cell::unsync::OnceCell; /// U::PKType. Table name is T_ManyToMany_foo where foo is the name of /// the Many field // -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct Many where T: DataObject, { - item_table: &'static str, + item_table: Cow<'static, str>, owner: Option, owner_type: SqlType, + #[serde(skip)] new_values: Vec, + #[serde(skip)] + #[serde(default = "default_oc")] all_values: OnceCell>, } impl Many @@ -33,7 +42,7 @@ where /// [`DataObject`]: super::DataObject pub fn new() -> Self { Many { - item_table: "not_initialized", + item_table: Cow::Borrowed("not_initialized"), owner: None, owner_type: SqlType::Int, new_values: Vec::new(), @@ -46,7 +55,7 @@ where if self.owner.is_some() { return; } - self.item_table = item_table; + self.item_table = Cow::Borrowed(item_table); self.owner = Some(owner); self.owner_type = owner_type; self.all_values = OnceCell::new(); @@ -72,7 +81,7 @@ where let owner = self.owner.as_ref().ok_or(Error::NotInitialized)?; while !self.new_values.is_empty() { conn.insert_only( - self.item_table, + &self.item_table, &self.columns(), &[ owner.as_ref(), @@ -96,7 +105,7 @@ where let mut vals = T::query() .filter(BoolExpr::Subquery { col: T::PKCOL, - tbl2: self.item_table, + tbl2: self.item_table.clone(), tbl2_col: "has", expr: Box::new(BoolExpr::Eq("owner", Expr::Val(owner.clone()))), }) diff --git a/butane_core/src/query/fieldexpr.rs b/butane_core/src/query/fieldexpr.rs index 618ff973..91ed621e 100644 --- a/butane_core/src/query/fieldexpr.rs +++ b/butane_core/src/query/fieldexpr.rs @@ -4,7 +4,7 @@ use crate::fkey::ForeignKey; use crate::query::{BoolExpr, Column, Expr, Join}; use crate::sqlval::{FieldType, SqlVal, ToSql}; use crate::DataObject; -use std::borrow::Borrow; +use std::borrow::{Borrow, Cow}; use std::cmp::{PartialEq, PartialOrd}; use std::marker::PhantomData; @@ -78,7 +78,7 @@ impl FieldExpr> { pub fn subfilter(&self, q: BoolExpr) -> BoolExpr { BoolExpr::Subquery { col: self.name, - tbl2: F::TABLE, + tbl2: Cow::Borrowed(F::TABLE), tbl2_col: F::PKCOL, expr: Box::new(q), } @@ -119,7 +119,7 @@ where //let many_tbl = format!("{}_{}_Many", O::TABLE, self.name); BoolExpr::SubqueryJoin { col: O::PKCOL, - tbl2: T::TABLE, + tbl2: Cow::Borrowed(T::TABLE), col2: Column::new(self.many_table, "owner"), joins: vec![Join::Inner { join_table: self.many_table, diff --git a/butane_core/src/query/mod.rs b/butane_core/src/query/mod.rs index f37c61c1..68d61d29 100644 --- a/butane_core/src/query/mod.rs +++ b/butane_core/src/query/mod.rs @@ -5,12 +5,15 @@ use crate::db::{BackendRows, ConnectionMethods, QueryResult}; use crate::{DataResult, Result, SqlVal}; use fallible_iterator::FallibleIterator; +use std::borrow::Cow; use std::marker::PhantomData; mod fieldexpr; pub use fieldexpr::{DataOrd, FieldExpr, ManyFieldExpr}; +type TblName = Cow<'static, str>; + /// Abstract representation of a database expression. #[derive(Clone)] pub enum Expr { @@ -44,7 +47,7 @@ pub enum BoolExpr { /// in `tbl2` is true. Subquery { col: &'static str, - tbl2: &'static str, + tbl2: TblName, tbl2_col: &'static str, expr: Box, }, @@ -54,7 +57,7 @@ pub enum BoolExpr { /// in `tbl2` with the specified joins is true. SubqueryJoin { col: &'static str, - tbl2: &'static str, + tbl2: TblName, col2: Column, joins: Vec, expr: Box, @@ -88,21 +91,21 @@ pub enum Join { #[derive(Clone)] pub struct Column { - table: Option<&'static str>, + table: Option, name: &'static str, } impl Column { pub fn new(table: &'static str, name: &'static str) -> Self { Column { - table: Some(table), + table: Some(Cow::Borrowed(table)), name, } } pub fn unqualified(name: &'static str) -> Self { Column { table: None, name } } - pub fn table(&self) -> Option<&'static str> { - self.table + pub fn table(&self) -> Option<&str> { + self.table.as_ref().map(|t| t.as_ref()) } pub fn name(&self) -> &'static str { self.name @@ -112,7 +115,7 @@ impl Column { /// Representation of a database query. #[derive(Clone)] pub struct Query { - table: &'static str, + table: TblName, filter: Option, limit: Option, sort: Vec, @@ -124,7 +127,7 @@ impl Query { /// `limit`. pub fn new(table: &'static str) -> Query { Query { - table, + table: Cow::Borrowed(table), filter: None, limit: None, sort: Vec::new(), @@ -168,7 +171,7 @@ impl Query { /// Executes the query against `conn` and returns the first result (if any). pub fn load_first(self, conn: &impl ConnectionMethods) -> Result> { - conn.query(self.table, T::COLUMNS, self.filter, Some(1), None)? + conn.query(&self.table, T::COLUMNS, self.filter, Some(1), None)? .mapped(T::from_row) .nth(0) } @@ -180,13 +183,13 @@ impl Query { } else { Some(self.sort.as_slice()) }; - conn.query(self.table, T::COLUMNS, self.filter, self.limit, sort)? + conn.query(&self.table, T::COLUMNS, self.filter, self.limit, sort)? .mapped(T::from_row) .collect() } /// Executes the query against `conn` and deletes all matching objects. pub fn delete(self, conn: &impl ConnectionMethods) -> Result { - conn.delete_where(self.table, self.filter.unwrap_or(BoolExpr::True)) + conn.delete_where(&self.table, self.filter.unwrap_or(BoolExpr::True)) } }