Skip to content

Commit

Permalink
Merge branch 'r/0.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
emirror-de committed Aug 26, 2021
2 parents 7f9f3c7 + 1dff86f commit 4f2341f
Show file tree
Hide file tree
Showing 20 changed files with 817 additions and 18 deletions.
22 changes: 5 additions & 17 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
[package]
name = "naphtha"
version = "0.0.0"
authors = ["Lewin Probst <[email protected]>"]
edition = "2018"
license = "MIT OR Apache-2.0"
description = "Work in progress"
homepage = "https://github.com/emirror-de/naphtha"
documentation = "https://github.com/emirror-de/naphtha"
repository = "https://github.com/emirror-de/naphtha"
readme = "README.md"
keywords = []
categories = []

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
[workspace]
members = [
"naphtha",
"naphtha-proc-macro",
]
26 changes: 26 additions & 0 deletions naphtha-proc-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "naphtha-proc-macro"
version = "0.1.0"
authors = ["Lewin Probst <[email protected]>"]
edition = "2018"
license = "MIT OR Apache-2.0"
description = "Supporting macro crate for naphtha"
homepage = "https://github.com/emirror-de/naphtha"
documentation = "https://github.com/emirror-de/naphtha"
repository = "https://github.com/emirror-de/naphtha"
readme = "README.md"
keywords = ["database", "models", "connection", "migration"]
categories = ["database"]

[lib]
proc-macro = true

[features]
default = []
sqlite = []
barrel-sqlite = []

[dependencies]
syn = { version = "^1.0.74", features = ["parsing"] }
proc-macro2 = "^1.0.28"
quote = "^1.0.9"
4 changes: 4 additions & 0 deletions naphtha-proc-macro/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# naphtha-proc-macro

This crate is a support crate that contains the necessary macros for naphtha to
compile.
2 changes: 2 additions & 0 deletions naphtha-proc-macro/src/barrel_impl/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[cfg(any(feature = "barrel-sqlite", feature = "barrel-full"))]
pub(crate) mod sqlite;
58 changes: 58 additions & 0 deletions naphtha-proc-macro/src/barrel_impl/sqlite.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use quote::quote;

pub(crate) fn impl_sqlite() -> ::proc_macro2::TokenStream {
quote! {
impl ::naphtha::barrel::DatabaseSqlMigrationExecutor<::diesel::SqliteConnection, usize> for Person
where
Self: ::naphtha::barrel::DatabaseSqlMigration,
{
fn execute_migration_up(conn: &::naphtha::DatabaseConnection<::diesel::SqliteConnection>) -> Result<usize, String> {
use {
::log::error,
::naphtha::{barrel::Migration, DatabaseConnection},
crate::diesel::RunQueryDsl,
};
let mut m = Migration::new();
Self::migration_up(&mut m);
let m = m.make::<::naphtha::barrel::backend::Sqlite>();

let c = match conn.lock() {
Ok(c) => c,
Err(msg) => {
error!("Could not aquire lock on DatabaseSqlMigrationExecutor::execute_migration_up: {}", msg.to_string());
return Err(msg.to_string());
}
};

match ::diesel::sql_query(m).execute(&*c) {
Ok(u) => Ok(u),
Err(msg) => Err(msg.to_string()),
}
}

fn execute_migration_down(conn: &::naphtha::DatabaseConnection<::diesel::SqliteConnection>) -> Result<usize, String> {
use {
::log::error,
::naphtha::{barrel::Migration, DatabaseConnection},
crate::diesel::RunQueryDsl,
};
let mut m = Migration::new();
Self::migration_down(&mut m);
let m = m.make::<::naphtha::barrel::backend::Sqlite>();

let c = match conn.lock() {
Ok(c) => c,
Err(msg) => {
error!("Could not aquire lock on DatabaseSqlMigrationExecutor::execute_migration_down for model: {}", msg.to_string());
return Err(msg.to_string());
}
};

match ::diesel::sql_query(m).execute(&*c) {
Ok(u) => Ok(u),
Err(msg) => Err(msg.to_string()),
}
}
}
}
}
2 changes: 2 additions & 0 deletions naphtha-proc-macro/src/database_impl/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[cfg(feature = "sqlite")]
pub(crate) mod sqlite;
163 changes: 163 additions & 0 deletions naphtha-proc-macro/src/database_impl/sqlite.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use {
quote::quote,
syn::{Data::Struct, DeriveInput},
};

pub(crate) fn impl_sqlite(
ast: &DeriveInput,
attr: &::proc_macro2::TokenStream,
) -> ::proc_macro2::TokenStream {
let database_modifier = impl_database_modifier(ast, attr);
quote! {
#database_modifier
}
}

fn impl_database_modifier(
ast: &DeriveInput,
table_name: &::proc_macro2::TokenStream,
) -> ::proc_macro2::TokenStream {
let name = &ast.ident;
let table_name = crate::helper::extract_table_name(table_name);
//let table_name: syn::UsePath = syn::parse_quote! {#name::table_name()};

let insert_properties = generate_insert_properties(ast);
if !crate::helper::has_id(ast) {
panic!("No `id` member found in model `{}`. Currently only models having an `id` column of type `i32` are supported.", name);
}

/*
let model_has_id_member = crate::helper::has_id(ast);
if (#model_has_id_member) {
#table_name.select(#table_name.primary_key())
.order(#table_name.primary_key().desc())
.first(&*c)
} else {
#table_name.select(#table_name.primary_key())
.filter(#table_name.primary_key().eq(self.primary_key()))
.first(&*c)
}
*/

quote! {
use {
::diesel::{backend::Backend, prelude::*},
::log::error,
::naphtha::{DatabaseModelModifier, DatabaseConnection},
};
impl DatabaseModelModifier<SqliteConnection> for #name
where
Self: ::naphtha::DatabaseUpdateHandler,
{
fn insert(&mut self, conn: &DatabaseConnection<SqliteConnection>) -> bool {
use {
::naphtha::DatabaseModel,
schema::{#table_name, #table_name::dsl::*},
};
// preventing duplicate insertion if default primary key gets
// changed on database insertion.
if self.primary_key() != Self::default_primary_key() {
return false;
}
let c = match conn.lock() {
Ok(c) => c,
Err(msg) => {
error!("Could not aquire lock on DatabaseModifier::insert for model:\n{:#?}", self);
return false;
}
};
let res_id = match c.transaction::<_, ::diesel::result::Error, _>(|| {
diesel::insert_into(#table_name)
.values((#insert_properties))
.execute(&*c)?;
#table_name.select(#table_name.primary_key())
.order(#table_name.primary_key().desc())
.first(&*c)
}) {
Ok(v) => v,
Err(msg) => {
error!("Failed inserting entity:\n{:#?}", self);
return false;
}
};
self.set_primary_key(&res_id);
true
}

fn update(&mut self, conn: &DatabaseConnection<SqliteConnection>) -> bool {
let c = match conn.lock() {
Ok(c) => c,
Err(msg) => {
error!("Could not aquire lock on DatabaseModifier::update for model:\n{:#?}", self);
return false;
}
};
self.before_update();
let update_result = match self.save_changes::<Self>(&*c) {
Ok(_) => true,
Err(msg) => {
error!("Failed updating entity:\n{:#?}", self);
return false;
},
};
self.after_update();
update_result
}

fn remove(self, conn: &DatabaseConnection<SqliteConnection>) -> bool {
use {
::log::info,
::naphtha::DatabaseModel,
schema::{#table_name, #table_name::dsl::*},
};
let c = match conn.lock() {
Ok(c) => c,
Err(msg) => {
error!("Could not aquire lock on DatabaseModifier::remove for model:\n{:#?}", self);
return false;
}
};
let num_deleted = ::diesel::delete(
#table_name.filter(
#table_name.primary_key().eq(self.primary_key())
)
);
match num_deleted.execute(&*c) {
Ok(_) => {
#[cfg(debug_assertions)]
info!("Removed entity with primary key {} from database!", self.primary_key());
true
},
Err(msg) => {
error!("Could not aquire lock on DatabaseModifier::remove for model:\n{:#?}", self);
false
}
}
}
}
}
}

fn generate_insert_properties(ast: &DeriveInput) -> ::proc_macro2::TokenStream {
let data = match &ast.data {
Struct(data) => data,
_ => panic!("Other data formats than \"struct\" is not supported yet!"),
};
let mut collected_properties = quote! {};
for field in data.fields.iter() {
if field.ident.is_none() {
continue;
}
let fieldname = field.ident.as_ref().unwrap();
if &fieldname.to_string()[..] == "id" {
// field id is currently used as primary key and therfore generated
// by the database, so it must not be set during insertion.
continue;
}
collected_properties = quote! {
#collected_properties
#fieldname.eq(&self.#fieldname),
};
}
collected_properties
}
56 changes: 56 additions & 0 deletions naphtha-proc-macro/src/helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use syn::{Data::Struct, DeriveInput, Ident};

pub fn extract_table_name(attr: &::proc_macro2::TokenStream) -> Ident {
for t in attr.clone().into_iter() {
match t {
::proc_macro2::TokenTree::Group(g) => {
let mut is_next = false;
for t in g.stream().into_iter() {
match t {
::proc_macro2::TokenTree::Ident(i) => {
let ident = ::proc_macro2::Ident::new(
"table_name",
::proc_macro2::Span::call_site(),
);
is_next = ident == i;
}
::proc_macro2::TokenTree::Literal(l) => {
if is_next {
let l = l.to_string();
return Ident::new(
&l[1..l.len() - 1],
::proc_macro2::Span::call_site(),
);
}
}
_ => continue,
}
}
}
_ => continue,
}
}

panic!("Attribute table_name has not been found in {}", attr);
}

pub fn has_id(ast: &DeriveInput) -> bool {
let data = match &ast.data {
Struct(data) => data,
_ => {
return false;
}
};
for field in data.fields.iter() {
if field.ident.is_none() {
continue;
}
let fieldname = field.ident.as_ref().unwrap();
match &fieldname.to_string()[..] {
"id" => return true,
_ => continue,
};
}

false
}
47 changes: 47 additions & 0 deletions naphtha-proc-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
extern crate proc_macro;
extern crate quote;

use {
quote::quote,
syn::{parse, DeriveInput},
};

#[cfg(any(feature = "barrel-full", feature = "barrel-sqlite",))]
mod barrel_impl;
mod database_impl;
#[allow(dead_code)]
mod helper;

#[proc_macro_attribute]
pub fn model(
attr: ::proc_macro::TokenStream,
item: ::proc_macro::TokenStream,
) -> ::proc_macro::TokenStream {
let ast: DeriveInput = parse(item).expect(
"proc_macro_attribute model: Could not parse TokenStream input!",
);
let attr = format!("#[{}]", attr);
let attr: ::proc_macro2::TokenStream = attr.parse().unwrap();

#[cfg(not(feature = "sqlite"))]
let impl_sqlite = quote! {};
#[cfg(feature = "sqlite")]
let impl_sqlite = database_impl::sqlite::impl_sqlite(&ast, &attr);

#[cfg(not(any(feature = "barrel-full", feature = "barrel-sqlite")))]
let impl_barrel_sqlite = quote! {};
#[cfg(any(feature = "barrel-full", feature = "barrel-sqlite"))]
let impl_barrel_sqlite = barrel_impl::sqlite::impl_sqlite();

let output = quote! {
use schema::*;
#[derive(Debug, Queryable, Identifiable, AsChangeset, Associations)]
#attr
#ast

#impl_sqlite
#impl_barrel_sqlite
};

::proc_macro::TokenStream::from(output)
}
Loading

0 comments on commit 4f2341f

Please sign in to comment.