aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMelody Horn / boringcactus <melody@boringcactus.com>2021-06-23 21:07:56 -0600
committerMelody Horn / boringcactus <melody@boringcactus.com>2021-06-23 21:07:56 -0600
commit8852f0b7090c612f1d04a60becb55bbe56441eda (patch)
treef9fe2c8d3782ad2c09bb9fa4171ecb0653cc92b0
parent88d527d574567420d7aa72f37205bd6b345b8dd7 (diff)
downloadtosin-8852f0b7090c612f1d04a60becb55bbe56441eda.tar.gz
tosin-8852f0b7090c612f1d04a60becb55bbe56441eda.zip
start impling diesel::Queryable for Models
-rw-r--r--Cargo.toml4
-rw-r--r--examples/tutorial02/main.rs37
-rw-r--r--examples/tutorial02/polls/models.rs4
-rw-r--r--src/db/backend.rs8
-rw-r--r--tosin-macros/src/lib.rs97
5 files changed, 118 insertions, 32 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 76282f6..6b9d7fb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,12 +8,12 @@ edition = "2018"
[dependencies]
barrel = { version = "0.6.5", features = ["sqlite3"] }
-diesel = { version = "1.4.4", features = ["sqlite"] }
+chrono = "0.4"
+diesel = { version = "1.4.4", features = ["sqlite", "chrono"] }
hyper = { version = "0.14", features = ["full"] }
quote = "1.0"
structopt = "0.3.21"
syn = { version = "1.0", features = ["full", "extra-traits"] }
-time = "0.2.27"
tokio = { version = "1", features = ["full"] }
tosin-macros = { version = "0.1.0", path = "./tosin-macros" }
warp = "0.3"
diff --git a/examples/tutorial02/main.rs b/examples/tutorial02/main.rs
index 8f0eebe..bac20e1 100644
--- a/examples/tutorial02/main.rs
+++ b/examples/tutorial02/main.rs
@@ -25,3 +25,40 @@ fn settings() -> Settings<impl Connectable> {
}
tosin::main!(urls(), settings());
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use diesel::prelude::*;
+
+ #[test]
+ fn test_models() {
+ use polls::models::{Choice, Question, choice, question};
+
+ let settings = settings();
+ let connection = settings.database.connect().unwrap();
+
+ // get the list of all questions
+ let all_questions = question::table.load::<Question>(&connection).unwrap();
+ assert!(all_questions.is_empty());
+
+ // make a new one
+ let mut q = Question::new(
+ "What's new?".to_string(),
+ time::PrimitiveDateTime::now(),
+ );
+ // save it
+ q.save_mut();
+ // it's got an id now!
+ assert!(q.id().is_some());
+ // it's still got all the same fields!
+ assert_eq!(q.question_text(), "What's new?");
+ // we can change them!
+ q.set_question_text("What's up?");
+ q.save_mut();
+
+ // it should be in the list now, too!!
+ let all_questions = question::table.load::<Question>(&connection).unwrap();
+ assert_eq!(all_questions, vec![q]);
+ }
+}
diff --git a/examples/tutorial02/polls/models.rs b/examples/tutorial02/polls/models.rs
index fb8be8f..d62f219 100644
--- a/examples/tutorial02/polls/models.rs
+++ b/examples/tutorial02/polls/models.rs
@@ -1,6 +1,6 @@
use tosin::db::models::{Model, Id, gather};
-#[derive(Model)]
+#[derive(Model, PartialEq, Debug)]
pub struct Question {
id: Option<Id>,
#[model(max_length=200)]
@@ -9,7 +9,7 @@ pub struct Question {
pub_date: time::PrimitiveDateTime,
}
-#[derive(Model)]
+#[derive(Model, PartialEq, Debug)]
pub struct Choice {
id: Option<Id>,
#[model(Question, on_delete=Cascade)]
diff --git a/src/db/backend.rs b/src/db/backend.rs
index 5f35dd9..5950747 100644
--- a/src/db/backend.rs
+++ b/src/db/backend.rs
@@ -1,8 +1,14 @@
use barrel::backend::SqlGenerator;
pub use diesel::connection::Connection;
+pub trait UsableBackend: diesel::backend::Backend where *const str: diesel::deserialize::FromSql<diesel::sql_types::Text, Self> {}
+impl UsableBackend for diesel::sqlite::Sqlite {}
+
+pub trait UsableConnection: Connection where <Self as Connection>::Backend: UsableBackend {}
+impl UsableConnection for diesel::sqlite::SqliteConnection {}
+
pub trait Connectable {
- type Connection: Connection;
+ type Connection: UsableConnection;
type SqlGenerator: SqlGenerator;
fn connect(&self) -> diesel::ConnectionResult<Self::Connection>;
}
diff --git a/tosin-macros/src/lib.rs b/tosin-macros/src/lib.rs
index c9e352c..131888c 100644
--- a/tosin-macros/src/lib.rs
+++ b/tosin-macros/src/lib.rs
@@ -18,9 +18,35 @@ pub fn model_derive(input: TokenStream) -> TokenStream {
impl_model(&ast)
}
-// TODO clean all this shit up
+struct FieldInfo<T: quote::ToTokens> {
+ tosin_field: T,
+ diesel_column: T,
+ diesel_type: T,
+}
+
+struct FieldsInfo<T: quote::ToTokens> {
+ tosin_fields: Vec<T>,
+ diesel_columns: Vec<T>,
+ diesel_types: Vec<T>,
+}
-fn to_field_spec(field: &syn::Field) -> (impl quote::ToTokens, impl quote::ToTokens) {
+impl<T: quote::ToTokens> std::iter::FromIterator<FieldInfo<T>> for FieldsInfo<T> {
+ fn from_iter<I: IntoIterator<Item=FieldInfo<T>>>(iter: I) -> Self {
+ let mut result = Self {
+ tosin_fields: vec![],
+ diesel_columns: vec![],
+ diesel_types: vec![],
+ };
+ for info in iter {
+ result.tosin_fields.push(info.tosin_field);
+ result.diesel_columns.push(info.diesel_column);
+ result.diesel_types.push(info.diesel_type);
+ }
+ result
+ }
+}
+
+fn to_field_spec(field: &syn::Field) -> FieldInfo<impl quote::ToTokens> {
fn parse_type(ty: &str) -> syn::Type {
syn::parse_str(ty).unwrap()
}
@@ -41,42 +67,48 @@ fn to_field_spec(field: &syn::Field) -> (impl quote::ToTokens, impl quote::ToTok
})
.collect();
if field_type == &parse_type("Option<Id>") {
- (
- quote! { ::tosin::db::models::Field::IntField { name: stringify!(#field_name) } },
- quote! { #field_name -> Integer }
- )
+ FieldInfo {
+ tosin_field: quote! { ::tosin::db::models::Field::IntField { name: stringify!(#field_name) } },
+ diesel_column: quote! { #field_name -> Integer },
+ diesel_type: quote! { ::tosin::db::sql_types::Integer },
+ }
} else if field_type == &parse_type("Id") {
// TODO foreign key constraint
- (
- quote! { ::tosin::db::models::Field::IntField { name: stringify!(#field_name) } },
- quote! { #field_name -> Integer }
- )
+ FieldInfo {
+ tosin_field: quote! { ::tosin::db::models::Field::IntField { name: stringify!(#field_name) } },
+ diesel_column: quote! { #field_name -> Integer },
+ diesel_type: quote! { ::tosin::db::sql_types::Integer },
+ }
} else if field_type == &parse_type("usize") {
// TODO default
- (
- quote! { ::tosin::db::models::Field::IntField { name: stringify!(#field_name) } },
- quote! { #field_name -> Integer }
- )
+ FieldInfo {
+ tosin_field: quote! { ::tosin::db::models::Field::IntField { name: stringify!(#field_name) } },
+ diesel_column: quote! { #field_name -> Integer },
+ diesel_type: quote! { ::tosin::db::sql_types::Integer },
+ }
} else if field_type == &parse_type("String") {
let max_length = model_options.iter()
.find(|(name, _value)| name.get_ident().map_or(false, |path| path == "max_length"))
.map(|(_name, value)| value);
if let Some(max_length) = max_length {
- (
- quote! { ::tosin::db::models::Field::CharField { name: stringify!(#field_name), max_length: Some(#max_length) } },
- quote! { #field_name -> Text }
- )
+ FieldInfo {
+ tosin_field: quote! { ::tosin::db::models::Field::CharField { name: stringify!(#field_name), max_length: Some(#max_length) } },
+ diesel_column: quote! { #field_name -> Text },
+ diesel_type: quote! { ::tosin::db::sql_types::Text }
+ }
} else {
- (
- quote! { ::tosin::db::models::Field::CharField { name: stringify!(#field_name), max_length: None } },
- quote! { #field_name -> Text }
- )
+ FieldInfo {
+ tosin_field: quote! { ::tosin::db::models::Field::CharField { name: stringify!(#field_name), max_length: None } },
+ diesel_column: quote! { #field_name -> Text },
+ diesel_type: quote! { ::tosin::db::sql_types::Text }
+ }
}
} else if field_type == &parse_type("time::PrimitiveDateTime") {
- (
- quote! { ::tosin::db::models::Field::DateTimeField { name: stringify!(#field_name) } },
- quote! { #field_name -> Timestamp }
- )
+ FieldInfo {
+ tosin_field: quote! { ::tosin::db::models::Field::DateTimeField { name: stringify!(#field_name) } },
+ diesel_column: quote! { #field_name -> Timestamp },
+ diesel_type: quote! { ::tosin::db::sql_types::Timestamp }
+ }
} else {
use quote::ToTokens;
panic!("can't handle {}", field.to_token_stream())
@@ -91,7 +123,8 @@ fn impl_model(ast: &syn::DeriveInput) -> TokenStream {
} else {
panic!("not on a struct");
};
- let (tosin_fields, diesel_columns): (Vec<_>, Vec<_>) = ast_data.fields.iter().map(to_field_spec).unzip();
+ let real_types: Vec<_> = ast_data.fields.iter().map(|field| &field.ty).collect();
+ let FieldsInfo { tosin_fields, diesel_columns, diesel_types } = ast_data.fields.iter().map(to_field_spec).collect();
let gen = quote! {
impl #name {
pub const META: ::tosin::db::models::ModelMeta = ::tosin::db::models::ModelMeta {
@@ -100,6 +133,16 @@ fn impl_model(ast: &syn::DeriveInput) -> TokenStream {
};
}
+ impl<__DB: tosin::db::diesel_backend::Backend, __ST> tosin::db::Queryable<__ST, __DB> for #name
+ where (#(#real_types),*): tosin::db::Queryable<__ST, __DB> {
+ type Row = <(#(#real_types),*) as tosin::db::Queryable<__ST, __DB>>::Row;
+
+ fn build(row: Self::Row) -> Self {
+ let row: (#(#real_types),*) = tosin::db::Queryable::build(row);
+ todo!()
+ }
+ }
+
// this means users need #[macro_use] extern crate diesel; but fuck doing it ourselves
table! {
#lowercase_name {