rough macro codegen

main
Ashelyn Dawn 2 months ago committed by Ashelyn Rose
parent 62c8eae231
commit 50110eb19f

17
Cargo.lock generated

@ -45,6 +45,12 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "anyhow"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
[[package]]
name = "async-stream"
version = "0.3.5"
@ -1400,6 +1406,7 @@ dependencies = [
name = "rust-forum-test"
version = "0.1.0"
dependencies = [
"anyhow",
"dotenv",
"rocket",
"serde",
@ -1813,9 +1820,11 @@ dependencies = [
name = "sqly"
version = "0.0.1"
dependencies = [
"anyhow",
"proc-macro2",
"quote",
"syn 2.0.51",
"thiserror",
]
[[package]]
@ -1889,18 +1898,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.57"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.57"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",

@ -14,6 +14,7 @@ time = { version = "0.3.34" }
uuid = { version = "1.7.0", features = ["v4", "serde"] }
serde_json = "1.0.114"
sqly = { path = "sqly" }
anyhow = "1.0.81" # should not need this once sqly is in its own crate
[workspace]
members = [

@ -10,4 +10,5 @@ proc-macro = true
proc-macro2 = "1.0.78"
quote = "1.0.35"
syn = "2.0.51"
anyhow = "1.0.81"
thiserror = "1.0.58"

@ -1,37 +1,66 @@
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{bracketed, parse_macro_input, Expr, Ident, LitStr, Result, Token};
use syn::{bracketed, parse_macro_input, Expr, Ident, LitStr, Token};
extern crate proc_macro;
extern crate self as sqly;
mod err {
use thiserror::Error;
#[derive(Error, Debug)]
pub enum QueryError {
#[error("Query returned no rows")]
RowNotFound,
#[error("Expected column {0} to have a value, but it was null")]
NullColumn(String),
#[error("Query returned too many rows, expected 1 but got {0}")]
TooManyRows(u32)
}
}
#[proc_macro]
pub fn query_parsed(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let macro_params = parse_macro_input!(input as QueryChecked);
let connection = macro_params.connection;
let query = macro_params.query;
let params = macro_params.params;
let return_type = macro_params.return_type;
let column_mapping = macro_params.column_mapping;
let return_type = get_return_type(&column_mapping);
let query = quote! {
let rows = sqlx::query!(#query, #params)
sqlx::query!(#query, #params)
.fetch_all(#connection)
.await?.iter()
.await.expect("Query failed").into_iter().peekable()
};
let parse = generate_parse_value(&return_type);
let parse = generate_parse_expression(&column_mapping);
let result = quote! {
{
#query;
#parse
let mut rows = #query;
let mut parse_closure = || -> anyhow::Result<#return_type> {
if rows.len() < 1 {
// anyhow::bail!(sqly::err::QueryError::RowNotFound)
panic!("Not enough rows");
}
let result = #parse;
Ok(result)
};
parse_closure()
}
};
proc_macro::TokenStream::from(result)
}
fn generate_parse_value(value: &TypeMapping) -> proc_macro2::TokenStream {
fn generate_parse_expression(value: &TypeMapping) -> proc_macro2::TokenStream {
match value.clone() {
TypeMapping::Column(name) => generate_parse_column(name),
TypeMapping::Vec(mapping) => generate_parse_vec(mapping),
@ -44,7 +73,10 @@ fn generate_parse_value(value: &TypeMapping) -> proc_macro2::TokenStream {
fn generate_parse_column(column: Ident) -> proc_macro2::TokenStream {
quote! {
rows[0].#column
{
let row = rows.peek().expect("Too few rows");
row.#column.clone()
}
}
}
@ -57,7 +89,7 @@ fn generate_parse_object(
.iter()
.map(|property: &TypeProperty| -> proc_macro2::TokenStream {
let name = &property.name;
let value = generate_parse_value(&property.mapping);
let value = generate_parse_expression(&property.mapping);
quote! {
#name: #value
}
@ -71,27 +103,31 @@ fn generate_parse_object(
}
fn generate_parse_vec(inner_type: Box<TypeMapping>) -> proc_macro2::TokenStream {
let value_expression = generate_parse_value(&*inner_type);
let value_expression = generate_parse_expression(&*inner_type);
quote! {
{
let vec = Vec::new();
let optional_value = #value_expression;
if optional_value.is_some() {
vec.push(#value_expression);
}
let mut vec = Vec::new();
vec.push(#value_expression);
vec
}
}
}
fn get_return_type(mapping: &TypeMapping) -> Ident {
match mapping {
TypeMapping::Column(_) => panic!("Cannot generate type mapping for a single column: Unknown type"),
TypeMapping::Vec(mapping) => get_return_type(mapping),
TypeMapping::Object { struct_name, properties: _ } => struct_name.clone(),
}
}
#[derive(Clone, Debug)]
struct QueryChecked {
connection: Expr,
query: LitStr,
params: Punctuated<Expr, Token![,]>,
return_type: TypeMapping,
column_mapping: TypeMapping,
}
#[derive(Clone, Debug)]
@ -111,7 +147,7 @@ struct TypeProperty {
}
impl Parse for QueryChecked {
fn parse(input: ParseStream) -> Result<Self> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut connection: Option<Expr> = None;
let mut query: Option<LitStr> = None;
let mut params: Option<Punctuated<Expr, Token![,]>> = None;
@ -145,13 +181,13 @@ impl Parse for QueryChecked {
connection: connection.ok_or(input.error("Expected connection property"))?,
query: query.ok_or(input.error("Expected query property"))?,
params: params.unwrap_or(Punctuated::new()),
return_type: return_type.ok_or(input.error("Expected return type property"))?,
column_mapping: return_type.ok_or(input.error("Expected return type property"))?,
})
}
}
impl Parse for TypeMapping {
fn parse(input: ParseStream) -> Result<Self> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let identifier = input.parse::<syn::Ident>()?;
if !input.is_empty() && input.peek(Token![<]) && identifier.to_string() == "Vec" {
@ -179,7 +215,7 @@ impl Parse for TypeMapping {
}
}
impl Parse for TypeProperty {
fn parse(input: ParseStream) -> Result<Self> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let identifier = input.parse::<syn::Ident>()?;
input.parse::<Token![:]>()?;
let property_type: TypeMapping = input.parse()?;

@ -30,7 +30,24 @@ impl DB {
let site = sqly::query_parsed!(
connection = &self.connection_pool,
query = r#"
select * from forum.site
select
site_uuid as "site_uuid!",
site_title as "site_title!",
site_base_url as "site_base_url!",
site_theme as "site_theme!",
board_uuid as "board_uuid!",
board_title as "board_title!",
board_description as "board_description!",
thread_uuid as "thread_uuid!",
thread_title as "thread_title!",
post_uuid as "post_uuid!",
post_contents as "post_contents!",
user_uuid as "user_uuid!",
user_email as "user_email!",
user_username as "user_username!",
user_password_hash as "user_password_hash!",
user_is_admin as "user_is_admin!"
from forum.site
left join forum.board on board_site = site_uuid
left join forum.thread on thread_board = board_uuid
left join forum.post on post_thread = thread_uuid
@ -65,7 +82,7 @@ impl DB {
}
);
if let Some(site) = site {
if let Ok(site) = site {
Ok(site)
} else {
Err("Could not find site in DB".to_string())

Loading…
Cancel
Save