Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
strategy:
fail-fast: false
matrix:
crate: [canyon_connection, canyon_crud, canyon_macros, canyon_observer]
crate: [canyon_connection, canyon_crud, canyon_macros, canyon_migrations]
steps:
- uses: actions/checkout@v3

Expand Down
17 changes: 11 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ description.workspace = true
members = [
"canyon_connection",
"canyon_crud",
"canyon_observer",
"canyon_entities",
"canyon_migrations",
"canyon_macros",

"tests"
Expand All @@ -23,7 +24,8 @@ members = [
# Project crates
canyon_connection = { workspace = true, path = "canyon_connection" }
canyon_crud = { workspace = true, path = "canyon_crud" }
canyon_observer = { workspace = true, path = "canyon_observer" }
canyon_entities = { workspace = true, path = "canyon_entities" }
canyon_migrations = { workspace = true, path = "canyon_migrations", optional = true }
canyon_macros = { workspace = true, path = "canyon_macros" }

# To be marked as opt deps
Expand All @@ -33,7 +35,8 @@ tiberius = { workspace = true, optional = true }
[workspace.dependencies]
canyon_crud = { version = "0.3.1", path = "canyon_crud" }
canyon_connection = { version = "0.3.1", path = "canyon_connection" }
canyon_observer = { version = "0.3.1", path = "canyon_observer" }
canyon_entities = { version = "0.3.1", path = "canyon_entities" }
canyon_migrations = { version = "0.3.1", path = "canyon_migrations"}
canyon_macros = { version = "0.3.1", path = "canyon_macros" }

tokio = { version = "1.27.0", features = ["full"] }
Expand All @@ -52,20 +55,22 @@ toml = "0.7.3"
async-trait = "0.1.68"
walkdir = "2.3.3"
regex = "1.5"
partialdebug = "0.2.0"

quote = "1.0.9"
proc-macro2 = "1.0.27"

[workspace.package]
version = "0.3.1"
edition = "2021"
authors = ["Alex Vergara<[email protected]>, Gonzalo Busto<gbm25@gmail.com>"]
authors = ["Alex Vergara<[email protected]>, Gonzalo Busto Musi<gonzalo.busto@gmail.com>"]
documentation = "https://zerodaycode.github.io/canyon-book/"
homepage = "https://github.com/zerodaycode/Canyon-SQL"
readme = "README.md"
license = "MIT"
description = "A Rust ORM and QueryBuilder"

[features]
postgres = ["tokio-postgres", "canyon_connection/postgres", "canyon_crud/postgres", "canyon_observer/postgres", "canyon_macros/postgres"]
mssql = ["tiberius", "canyon_connection/mssql", "canyon_crud/mssql", "canyon_observer/mssql", "canyon_macros/mssql"]
postgres = ["tokio-postgres", "canyon_connection/postgres", "canyon_crud/postgres", "canyon_migrations/postgres", "canyon_macros/postgres"]
mssql = ["tiberius", "canyon_connection/mssql", "canyon_crud/mssql", "canyon_migrations/mssql", "canyon_macros/mssql"]
migrations = ["canyon_migrations", "canyon_macros/migrations"]
2 changes: 1 addition & 1 deletion bash_aliases.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ alias SqlServerInitializationLinux='cargo test initialize_sql_server_docker_inst


# Publish Canyon-SQL to the registry with its dependencies
alias PublishCanyon='cargo publish -p canyon_connection && cargo publish -p canyon_crud && cargo publish -p canyon_observer && cargo publish -p canyon_macros && cargo publish -p canyon_sql_root'
alias PublishCanyon='cargo publish -p canyon_connection && cargo publish -p canyon_crud && cargo publish -p canyon_migrations && cargo publish -p canyon_macros && cargo publish -p canyon_sql_root'

# Collects the code coverage for the project (tests must run before this)
alias CcEnvVars='export CARGO_INCREMENTAL=0
Expand Down
17 changes: 17 additions & 0 deletions canyon_entities/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "canyon_entities"
version.workspace = true
edition.workspace = true
authors.workspace = true
documentation.workspace = true
homepage.workspace = true
readme.workspace = true
license.workspace = true
description.workspace = true

[dependencies]
regex = { workspace = true }
partialdebug = { workspace = true }
quote = { workspace = true }
proc-macro2 = { workspace = true }
syn = { version = "1.0.86", features = ["full", "parsing"] } # TODO Pending to refactor and upgrade
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use syn::{
use super::entity_fields::EntityField;

/// Provides a convenient way of handling the data on any
/// `CanyonEntity` struct anntotaded with the macro `#[canyon_entity]`
/// `CanyonEntity` struct annotated with the macro `#[canyon_entity]`
#[derive(PartialDebug, Clone)]
pub struct CanyonEntity {
pub struct_name: Ident,
Expand Down
11 changes: 11 additions & 0 deletions canyon_entities/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use crate::register_types::CanyonRegisterEntity;
use std::sync::Mutex;

pub mod entity;
pub mod entity_fields;
pub mod field_annotation;
pub mod manager_builder;
pub mod register_types;

pub static CANYON_REGISTER_ENTITIES: Mutex<Vec<CanyonRegisterEntity<'static>>> =
Mutex::new(Vec::new());
45 changes: 45 additions & 0 deletions canyon_entities/src/register_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/// This file contains `Rust` types that represents an entry on the `CanyonRegister`
/// where `Canyon` tracks the user types that has to manage

pub const NUMERIC_PK_DATATYPE: [&str; 6] = ["i16", "u16", "i32", "u32", "i64", "u64"];

/// Gets the necessary identifiers of a CanyonEntity to make it the comparative
/// against the database schemas
#[derive(Debug, Clone, Default)]
pub struct CanyonRegisterEntity<'a> {
pub entity_name: &'a str,
pub entity_db_table_name: &'a str,
pub user_schema_name: Option<&'a str>,
pub entity_fields: Vec<CanyonRegisterEntityField>,
}

/// Complementary type for a field that represents a struct field that maps
/// some real database column data
#[derive(Debug, Clone, Default)]
pub struct CanyonRegisterEntityField {
pub field_name: String,
pub field_type: String,
pub annotations: Vec<String>,
}

impl CanyonRegisterEntityField {
/// Return if the field is autoincremental
pub fn is_autoincremental(&self) -> bool {
let has_pk_annotation = self
.annotations
.iter()
.find(|a| a.starts_with("Annotation: PrimaryKey"));

let pk_is_autoincremental = match has_pk_annotation {
Some(annotation) => annotation.contains("true"),
None => false,
};

NUMERIC_PK_DATATYPE.contains(&self.field_type.as_str()) && pk_is_autoincremental
}

/// Return the nullability of a the field
pub fn is_nullable(&self) -> bool {
self.field_type.to_uppercase().starts_with("OPTION")
}
}
10 changes: 6 additions & 4 deletions canyon_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ proc-macro2 = { workspace = true }
futures = { workspace = true }
tokio = { workspace = true }

canyon_observer = { workspace = true }
canyon_crud = { workspace = true }
canyon_connection = { workspace = true }
canyon_crud = { workspace = true }
canyon_entities = { workspace = true }
canyon_migrations = { workspace = true, optional = true }

[features]
postgres = ["canyon_connection/postgres", "canyon_crud/postgres", "canyon_observer/postgres"]
mssql = ["canyon_connection/mssql", "canyon_crud/mssql", "canyon_observer/mssql"]
postgres = ["canyon_connection/postgres", "canyon_crud/postgres", "canyon_migrations/postgres"]
mssql = ["canyon_connection/mssql", "canyon_crud/mssql", "canyon_migrations/mssql"]
migrations = ["canyon_migrations"]
116 changes: 18 additions & 98 deletions canyon_macros/src/canyon_macro.rs
Original file line number Diff line number Diff line change
@@ -1,112 +1,32 @@
//! Provides helpers to build the `#[canyon_macros::canyon]` procedural like attribute macro

use proc_macro::TokenStream as TokenStream1;
use proc_macro2::{Ident, TokenStream};

use canyon_connection::CANYON_TOKIO_RUNTIME;
use canyon_migrations::migrations::handler::Migrations;
use canyon_migrations::{CM_QUERIES_TO_EXECUTE, QUERIES_TO_EXECUTE};
use proc_macro2::TokenStream;
use quote::quote;

use canyon_observer::{CM_QUERIES_TO_EXECUTE, QUERIES_TO_EXECUTE};
use syn::{Lit, NestedMeta};

#[derive(Debug)]
/// Utilery struct for wrapping the content and result of parsing the attributes on the `canyon` macro
pub struct CanyonMacroAttributes {
pub allowed_migrations: bool,
pub error: Option<TokenStream1>,
}

/// Parses the [`syn::NestedMeta::Meta`] or [`syn::NestedMeta::Lit`] attached to the `canyon` macro
pub fn parse_canyon_macro_attributes(_meta: &Vec<NestedMeta>) -> CanyonMacroAttributes {
let mut res = CanyonMacroAttributes {
allowed_migrations: false,
error: None,
};

for nested_meta in _meta {
match nested_meta {
syn::NestedMeta::Meta(m) => determine_allowed_attributes(m, &mut res),
syn::NestedMeta::Lit(lit) => match lit {
syn::Lit::Str(ref l) => {
res.error = Some(report_literals_not_allowed(&l.value(), lit))
}
syn::Lit::ByteStr(ref l) => {
res.error = Some(report_literals_not_allowed(
&String::from_utf8_lossy(&l.value()),
lit,
))
}
syn::Lit::Byte(ref l) => {
res.error = Some(report_literals_not_allowed(&l.value().to_string(), lit))
}
syn::Lit::Char(ref l) => {
res.error = Some(report_literals_not_allowed(&l.value().to_string(), lit))
}
syn::Lit::Int(ref l) => {
res.error = Some(report_literals_not_allowed(&l.to_string(), lit))
}
syn::Lit::Float(ref l) => {
res.error = Some(report_literals_not_allowed(&l.to_string(), lit))
}
syn::Lit::Bool(ref l) => {
res.error = Some(report_literals_not_allowed(&l.value().to_string(), lit))
}
syn::Lit::Verbatim(ref l) => {
res.error = Some(report_literals_not_allowed(&l.to_string(), lit))
}
},
}
}

res
}

/// Determines whenever a [`syn::NestedMeta::Meta`] it's classified as a valid argument of the `canyon` macro
fn determine_allowed_attributes(meta: &syn::Meta, cma: &mut CanyonMacroAttributes) {
const ALLOWED_ATTRS: [&str; 1] = ["enable_migrations"];

let attr_ident = meta.path().get_ident().unwrap();
let attr_ident_str = attr_ident.to_string();

if attr_ident_str.as_str() == "enable_migrations" {
cma.allowed_migrations = true;
} else {
let error = syn::Error::new_spanned(
Ident::new(&attr_ident_str, attr_ident.span()),
format!(
"No `{attr_ident_str}` arguments allowed in the `Canyon` macro attributes.\n\
Allowed ones are: {ALLOWED_ATTRS:?}"
),
)
.into_compile_error();
cma.error = Some(
quote! {
#error
fn main() {}
}
.into(),
)
}
}

/// Creates a custom error for report not allowed literals on the attribute
/// args of the `canyon` proc macro
fn report_literals_not_allowed(ident: &str, s: &Lit) -> TokenStream1 {
let error = syn::Error::new_spanned(
Ident::new(ident, s.span()),
"No literals allowed in the `Canyon` macro",
)
.into_compile_error();
#[cfg(feature = "migrations")]
pub fn main_with_queries() -> TokenStream {
CANYON_TOKIO_RUNTIME.block_on(async {
canyon_connection::init_connections_cache().await;
Migrations::migrate().await;
});

// The queries to execute at runtime in the managed state
let mut queries_tokens: Vec<TokenStream> = Vec::new();
wire_queries_to_execute(&mut queries_tokens);
quote! {
#error
fn main() {}
{
#(#queries_tokens)*
}
}
.into()
}

/// Creates a TokenScream that is used to load the data generated at compile-time
/// by the `CanyonManaged` macros again on the queries register
pub fn wire_queries_to_execute(canyon_manager_tokens: &mut Vec<TokenStream>) {
#[cfg(feature = "migrations")]
fn wire_queries_to_execute(canyon_manager_tokens: &mut Vec<TokenStream>) {
let cm_data = CM_QUERIES_TO_EXECUTE.lock().unwrap();
let data = QUERIES_TO_EXECUTE.lock().unwrap();

Expand Down
Loading