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}