From 76b63ec1a887e4cdb80ee84f4fe59164b968a380 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 6 May 2024 23:07:47 -0500 Subject: [PATCH] Test for proc macro reworking --- udf-macros/src/lib.rs | 8 +++ udf-macros/src/simplified.rs | 26 +++++++ udf-macros/tests/fail/shortform_not_pub.rs | 6 ++ .../tests/fail/shortform_not_pub.stderr | 7 ++ udf/src/argparse.rs | 72 +++++++++++++++++++ udf/src/lib.rs | 42 +++++------ udf/src/types/arg_list.rs | 24 ++----- udf/src/types/sql_types.rs | 20 +++++- 8 files changed, 160 insertions(+), 45 deletions(-) create mode 100644 udf-macros/src/simplified.rs create mode 100644 udf-macros/tests/fail/shortform_not_pub.rs create mode 100644 udf-macros/tests/fail/shortform_not_pub.stderr create mode 100644 udf/src/argparse.rs diff --git a/udf-macros/src/lib.rs b/udf-macros/src/lib.rs index 3951daf..f850e19 100644 --- a/udf-macros/src/lib.rs +++ b/udf-macros/src/lib.rs @@ -13,6 +13,7 @@ )] mod register; +mod simplified; mod types; use proc_macro::TokenStream; @@ -78,3 +79,10 @@ pub fn register(args: TokenStream, item: TokenStream) -> TokenStream { // Keep this file clean by keeping the dirty work in entry register::register(&args, item) } + +/// Apply `#[udf::udf]` to any function to make it a UDF +#[proc_macro_attribute] +pub fn udf(args: TokenStream, item: TokenStream) -> TokenStream { + // Keep this file clean by keeping the dirty work in entry + simplified::simple_udf(&args, item) +} diff --git a/udf-macros/src/simplified.rs b/udf-macros/src/simplified.rs new file mode 100644 index 0000000..f88d805 --- /dev/null +++ b/udf-macros/src/simplified.rs @@ -0,0 +1,26 @@ +use heck::AsSnakeCase; +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{format_ident, quote}; +use syn::parse::{Parse, ParseStream, Parser}; +use syn::punctuated::Punctuated; +use syn::{ + parse_macro_input, parse_quote, DeriveInput, Error, Expr, ExprLit, Ident, ImplItem, + ImplItemType, Item, ItemFn, ItemImpl, Lit, Meta, Path, PathSegment, Token, Type, TypePath, + TypeReference, Visibility, +}; + +use crate::match_variant; +use crate::types::{make_type_list, ImplType, RetType, TypeClass}; + +pub fn simple_udf(args: &TokenStream, input: TokenStream) -> TokenStream { + let parsed = parse_macro_input!(input as ItemFn); + if !matches!(parsed.vis, Visibility::Public(_)) { + return Error::new_spanned(parsed.vis, "UDFs must be marked `pub`") + .into_compile_error() + .into(); + } + // let sig = parsed.sig + // input + todo!() +} diff --git a/udf-macros/tests/fail/shortform_not_pub.rs b/udf-macros/tests/fail/shortform_not_pub.rs new file mode 100644 index 0000000..ab3ec05 --- /dev/null +++ b/udf-macros/tests/fail/shortform_not_pub.rs @@ -0,0 +1,6 @@ +// Functions must be public + +#[udf_macros::udf] +fn foo() {} + +fn main() {} diff --git a/udf-macros/tests/fail/shortform_not_pub.stderr b/udf-macros/tests/fail/shortform_not_pub.stderr new file mode 100644 index 0000000..870a182 --- /dev/null +++ b/udf-macros/tests/fail/shortform_not_pub.stderr @@ -0,0 +1,7 @@ +error: UDFs must be marked `pub` + --> tests/fail/shortform_not_pub.rs:3:1 + | +3 | #[udf_macros::udf] + | ^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `udf_macros::udf` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/udf/src/argparse.rs b/udf/src/argparse.rs new file mode 100644 index 0000000..09aa8ad --- /dev/null +++ b/udf/src/argparse.rs @@ -0,0 +1,72 @@ +//! Helpers for parsing args simply +use std::str; + +use crate::{SqlResult, SqlType}; + +// OPTIOANL ARGS specify these in the proc macro signature +pub enum Error<'res> { + InvalidType(SqlType), + UnexpectedNull, + Utf8(&'res [u8], str::Utf8Error), +} + +/// Types with this trait can easily be used as an argument +pub trait SqlArg<'res>: Sized { + /// How to set argument coercion + const COERCE_TYPE: SqlType; + + fn from_res(value: SqlResult<'res>) -> Result; +} + +impl<'res> SqlArg<'res> for &'res str { + const COERCE_TYPE: SqlType = SqlType::String; + + fn from_res(value: SqlResult<'res>) -> Result { + match value { + SqlResult::String(Some(v)) => Ok(str::from_utf8(v).map_err(|e| Error::Utf8(v, e))?), + SqlResult::Decimal(Some(s)) => Ok(s), + SqlResult::String(None) | SqlResult::Decimal(None) => todo!(), + SqlResult::Real(_) | SqlResult::Int(_) => Err(Error::InvalidType(value.as_type())), + } + } +} + +impl<'res> SqlArg<'res> for i64 { + const COERCE_TYPE: SqlType = SqlType::Int; + + fn from_res(value: SqlResult<'res>) -> Result { + match value { + SqlResult::Int(Some(v)) => Ok(v), + SqlResult::Int(None) => Err(Error::UnexpectedNull), + SqlResult::String(_) | SqlResult::Decimal(_) | SqlResult::Real(_) => { + Err(Error::InvalidType(value.as_type())) + } + } + } +} + +impl<'res> SqlArg<'res> for f64 { + const COERCE_TYPE: SqlType = SqlType::Real; + + fn from_res(value: SqlResult<'res>) -> Result { + match value { + SqlResult::Real(Some(v)) => Ok(v), + SqlResult::Real(None) => Err(Error::UnexpectedNull), + SqlResult::String(_) | SqlResult::Decimal(_) | SqlResult::Int(_) => { + Err(Error::InvalidType(value.as_type())) + } + } + } +} + +impl<'res, T: SqlArg<'res>> SqlArg<'res> for Option { + const COERCE_TYPE: SqlType = T::COERCE_TYPE; + + fn from_res(value: SqlResult<'res>) -> Result { + if value.is_null() { + Ok(None) + } else { + Ok(Some(T::from_res(value)?)) + } + } +} diff --git a/udf/src/lib.rs b/udf/src/lib.rs index d44b85b..6c71a04 100644 --- a/udf/src/lib.rs +++ b/udf/src/lib.rs @@ -120,26 +120,15 @@ //! issues compiling, be sure to update your toolchain. // Strict clippy -#![warn( - clippy::pedantic, - // clippy::cargo, - clippy::nursery, - clippy::str_to_string, - clippy::exhaustive_enums, - clippy::pattern_type_mismatch -)] +#![warn(clippy::pedantic)] +#![warn(clippy::nursery)] +#![warn(clippy::str_to_string)] +#![warn(clippy::exhaustive_enums)] +#![warn(clippy::pattern_type_mismatc)] // Pedantic config -#![allow( - clippy::missing_const_for_fn, - // clippy::missing_panics_doc, - clippy::must_use_candidate, - clippy::cast_possible_truncation -)] - -// We re-export this so we can use it in our macro, but don't need it -// to show up in our docs -#[doc(hidden)] -pub extern crate chrono; +#![allow(clippy::missing_const_for_fn)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::cast_possible_truncation)] #[doc(hidden)] pub extern crate udf_sys; @@ -150,17 +139,22 @@ pub use udf_macros::register; #[macro_use] mod macros; +mod argparse; +pub mod mock; pub mod prelude; pub mod traits; pub mod types; - -// We hide this because it's really only used by our proc macros -#[doc(hidden)] -pub mod wrapper; +mod wrapper; #[doc(inline)] pub use traits::*; #[doc(inline)] pub use types::{MYSQL_ERRMSG_SIZE, *}; -pub mod mock; +#[doc(hidden)] +pub mod internal { + pub use {chrono, udf_sys}; + + // pub use super::argparse::*; + pub use super::wrapper; +} diff --git a/udf/src/types/arg_list.rs b/udf/src/types/arg_list.rs index f781778..29e75d4 100644 --- a/udf/src/types/arg_list.rs +++ b/udf/src/types/arg_list.rs @@ -161,30 +161,14 @@ mod tests { // Verify no size issues #[test] fn args_size_init() { - assert_eq!( - size_of::(), - size_of::>(), - concat!("Size of: ", stringify!(UDF_ARGS)) - ); - assert_eq!( - align_of::(), - align_of::>(), - concat!("Alignment of ", stringify!(UDF_ARGS)) - ); + assert_eq!(size_of::(), size_of::>(),); + assert_eq!(align_of::(), align_of::>(),); } // Verify no size issues #[test] fn args_size_process() { - assert_eq!( - size_of::(), - size_of::>(), - concat!("Size of: ", stringify!(UDF_ARGS)) - ); - assert_eq!( - align_of::(), - align_of::>(), - concat!("Alignment of ", stringify!(UDF_ARGS)) - ); + assert_eq!(size_of::(), size_of::>(),); + assert_eq!(align_of::(), align_of::>(),); } } diff --git a/udf/src/types/sql_types.rs b/udf/src/types/sql_types.rs index 13a904e..e9f6c57 100644 --- a/udf/src/types/sql_types.rs +++ b/udf/src/types/sql_types.rs @@ -126,7 +126,7 @@ impl TryFrom<&SqlResult<'_>> for SqlType { /// /// This enum is labeled `non_exhaustive` to leave room for future types and /// coercion options. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] #[non_exhaustive] pub enum SqlResult<'a> { // INVALID_RESULT and ROW_RESULT are other options, but not valid for UDFs @@ -209,6 +209,15 @@ impl<'a> SqlResult<'a> { matches!(*self, Self::Decimal(_)) } + /// Check if this argument is a null type + #[inline] + pub fn is_null(&self) -> bool { + matches!( + self, + Self::String(None) | Self::Real(None) | Self::Int(None) | Self::Decimal(None) + ) + } + /// Return this type as an integer if possible /// /// This will exist if the variant is [`SqlResult::Int`], and it contains a @@ -267,4 +276,13 @@ impl<'a> SqlResult<'a> { _ => None, } } + + pub(crate) fn as_type(&'a self) -> SqlType { + match self { + SqlResult::String(_) => SqlType::String, + SqlResult::Real(_) => SqlType::Real, + SqlResult::Int(_) => SqlType::Int, + SqlResult::Decimal(_) => SqlType::Decimal, + } + } }