include_oracle_sql_args/
lib.rs

1//! This crate provides helper proc-macro(s) that assists [include-oracle-sql][1] in generating Yesql-like methods for using SQL in Rust.
2//!
3//! [1]: https://github.com/quietboil/include-oracle-sql
4
5use proc_macro;
6use proc_macro2::{TokenStream, Literal, Group, Delimiter, Punct, Spacing};
7use syn::{Token, parse::{Parse, ParseStream}};
8use quote::TokenStreamExt;
9
10/**
11Returns the uppercase equivalent of this identifier as a string literal.
12
13```
14let as_literal = include_oracle_sql_args::to_uppercase!(param_name);
15assert_eq!(as_literal, "PARAM_NAME");
16```
17*/
18#[proc_macro]
19pub fn to_uppercase(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
20    let in_arg = syn::parse_macro_input!(input as syn::Ident);
21    let in_name = in_arg.to_string();
22    let out_name = in_name.to_uppercase();
23    let mut tokens = TokenStream::new();
24    tokens.append(Literal::string(&out_name));
25    tokens.into()
26}
27
28/**
29Maps arguments of the generated database access method into a tuple of SQL arguments.
30
31## Examples
32
33### Single Argument
34
35```
36let arg = 42;
37let args = include_oracle_sql_args::map!(arg => "SELECT " :arg " FROM xxx WHERE x = " :arg " OR y = " :arg " ORDER BY z");
38assert_eq!(args, 42);
39```
40
41### Exactly 2 Parameters
42
43```
44let a1 = 27;
45let a2 = "name";
46let args = include_oracle_sql_args::map!(a1 a2 => "SELECT * FROM xxx WHERE a = " :a1 " AND b = " :a2);
47assert_eq!(args, (27, "name", ()));
48```
49
50### Unique SQL Parameters
51
52```
53let a1 = 31;
54let a2 = "text";
55let a3 = &["a", "b", "c"];
56let args = include_oracle_sql_args::map!(a1 a2 a3 => "UPDATE xxx SET a = " :a1 ", :b = " :a2 " WHERE c IN (" #a3 ")");
57assert_eq!(args, (31, "text", &["a", "b", "c"]));
58```
59
60### Duplicate SQL Parameters
61
62```
63let id = 19;
64let name = "unknown";
65let data = 3.14;
66let args = include_oracle_sql_args::map!(id name data => "UPDATE xxx SET a = " :name ", b = " :name ", c = " :data " WHERE i = " :id " OR ( x = " :name " AND i != " :id ")");
67assert_eq!(args, (
68    ("ID",   19),
69    ("NAME", "unknown"),
70    ("DATA", 3.14),
71));
72```
73
74### Reordered SQL Parameters
75
76```
77let a1 = 31;
78let a2 = "text";
79let a3 = &["a", "b", "c"];
80let args = include_oracle_sql_args::map!(a1 a2 a3 => "UPDATE xxx SET a = " :a2 ", :b = " :a1 " WHERE c IN (" #a3 ")");
81assert_eq!(args, (
82    ("A1", 31), 
83    ("A2", "text"),
84    ("A3", &["a", "b", "c"]),
85));
86```
87
88### Mutable (OUT) Argument
89
90```
91let id = 101;
92let mut name = String::new();
93let out_name = &mut name;
94let args = include_oracle_sql_args::map!(id out_name => "UPDATE xxx SET x = x || 'X' WHERE i = " :id " RETURN x INTO " :out_name);
95assert_eq!(args.0, 101);
96assert_eq!(args.2, ());
97// emulate output
98args.1.push_str("TestX");
99assert_eq!(name, "TestX");
100```
101*/
102#[proc_macro]
103pub fn map(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
104    let MapArgs { mut method_args, sql_args } = syn::parse_macro_input!(input as MapArgs);
105
106    let mut tokens = TokenStream::new();
107
108    if method_args.len() == 1 {
109        tokens.append(method_args.remove(0));
110    } else if method_args == sql_args {
111        let mut items = TokenStream::new();
112        if method_args.len() == 2 {
113            items.append_terminated(method_args, Punct::new(',', Spacing::Alone));
114            let unit = TokenStream::new();
115            items.append(Group::new(Delimiter::Parenthesis, unit));
116        } else {
117            items.append_separated(method_args, Punct::new(',', Spacing::Alone));
118        }
119        tokens.append(Group::new(Delimiter::Parenthesis, items));
120    } else {
121        let mut items = TokenStream::new();
122        for arg in method_args {
123            if !items.is_empty() {
124                items.append(Punct::new(',', Spacing::Alone));
125            }
126            let mut name_value = TokenStream::new();
127            name_value.append(Literal::string(arg.to_string().to_uppercase().as_str()));
128            name_value.append(Punct::new(',', Spacing::Alone));
129            name_value.append(arg);
130            items.append(Group::new(Delimiter::Parenthesis, name_value))
131        }
132        tokens.append(Group::new(Delimiter::Parenthesis, items));
133    }
134    tokens.into()
135}
136
137struct MapArgs {
138    method_args: Vec<syn::Ident>,
139    sql_args: Vec<syn::Ident>,
140}
141
142impl Parse for MapArgs {
143    fn parse(input: ParseStream) -> syn::Result<Self> {
144        let mut method_args = Vec::new();
145        while !input.peek(Token![=>]) {
146            let arg = input.parse()?;
147            method_args.push(arg);
148        }
149        let _ : Token![=>] = input.parse()?;
150        let mut sql_args = Vec::new();
151        while !input.is_empty() {
152            let _ : syn::LitStr = input.parse()?;
153            if input.is_empty() {
154                break;
155            }
156            let variant = input.lookahead1();
157            if variant.peek(Token![:]) {
158                let _ : Token![:] = input.parse()?;
159            } else if variant.peek(Token![#]) {
160                let _ : Token![#] = input.parse()?;
161            } else {
162                return Err(variant.error());
163            }
164            let arg = input.parse()?;
165            sql_args.push(arg);
166        }
167        Ok(Self { method_args, sql_args })
168    }
169}