|
|
|
@ -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()?;
|
|
|
|
|