1use proc_macro2::{Ident, Span};
2use quote::{quote, ToTokens};
3use std::collections::{BTreeMap, HashMap};
4use std::fs::File;
5use std::io::Read;
6use std::path::PathBuf;
7use syn::{ItemFn, LitStr};
8use url::Url;
9
10use crate::codegen::loader_html::{load_html, Element};
11use crate::codegen::proc_macro::TokenStream;
12use crate::codegen::string_util::find_convert_string;
13use crate::codegen::ParseArgs;
14
15use crate::error::Error;
16
17pub fn load_mapper_map(html: &str) -> Result<BTreeMap<String, Element>, Error> {
19 let datas = load_mapper_vec(html)?;
20 let mut sql_map = BTreeMap::new();
21 let datas = include_replace(datas, &mut sql_map);
22 let mut m = BTreeMap::new();
23 for x in datas {
24 if let Some(v) = x.attrs.get("id") {
25 m.insert(v.to_string(), x);
26 }
27 }
28 Ok(m)
29}
30
31pub fn load_mapper_vec(html: &str) -> Result<Vec<Element>, Error> {
33 let datas = load_html(html).map_err(|e| Error::from(e.to_string()))?;
34 let mut mappers = vec![];
35 for x in datas {
36 if x.tag.eq("mapper") {
37 for x in x.childs {
38 mappers.push(x);
39 }
40 } else {
41 mappers.push(x);
42 }
43 }
44 Ok(mappers)
45}
46
47pub fn parse_html(html: &str, fn_name: &str, ignore: &mut Vec<String>) -> proc_macro2::TokenStream {
49 let html = html
50 .replace("\\\"", "\"")
51 .replace("\\n", "\n")
52 .trim_start_matches("\"")
53 .trim_end_matches("\"")
54 .to_string();
55 let datas = load_mapper_map(&html).expect(&format!("laod html={} fail", html));
56 match datas.into_iter().next() {
57 None => {
58 panic!("html not find fn:{}", fn_name);
59 }
60 Some((_, v)) => {
61 let node = parse_html_node(vec![v], ignore, fn_name);
62 return node;
63 }
64 }
65}
66
67fn include_replace(htmls: Vec<Element>, sql_map: &mut BTreeMap<String, Element>) -> Vec<Element> {
68 let mut results = vec![];
69 for mut x in htmls {
70 match x.tag.as_str() {
71 "sql" => {
72 sql_map.insert(
73 x.attrs
74 .get("id")
75 .expect("[rbatis-codegen] <sql> element must have id!")
76 .clone(),
77 x.clone(),
78 );
79 }
80 "include" => {
81 let ref_id = x
82 .attrs
83 .get("refid")
84 .expect(
85 "[rbatis-codegen] <include> element must have attr <include refid=\"\">!",
86 )
87 .clone();
88 let url;
89 if ref_id.contains("://") {
90 url = Url::parse(&ref_id).expect(&format!(
91 "[rbatis-codegen] parse <include refid=\"{}\"> fail!",
92 ref_id
93 ));
94 } else {
95 url = Url::parse(&format!("current://current?refid={}", ref_id)).expect(
96 &format!(
97 "[rbatis-codegen] parse <include refid=\"{}\"> fail!",
98 ref_id
99 ),
100 );
101 }
102 let mut manifest_dir =
103 std::env::var("CARGO_MANIFEST_DIR").expect("Failed to read CARGO_MANIFEST_DIR");
104 manifest_dir.push_str("/");
105
106 let path = url.host_str().unwrap_or_default().to_string()
107 + url.path().trim_end_matches("/").trim_end_matches("\\");
108 let mut file_path = PathBuf::from(&path);
109 if file_path.is_relative() {
110 file_path = PathBuf::from(format!("{}{}", manifest_dir, path));
111 }
112
113 match url.scheme() {
114 "file" => {
115 let mut ref_id = ref_id.clone();
116 let mut have_ref_id = false;
117 for (k, v) in url.query_pairs() {
118 if k.eq("refid") {
119 ref_id = v.to_string();
120 have_ref_id = true;
121 }
122 }
123 if !have_ref_id {
124 panic!("not find ref_id on url {}", ref_id);
125 }
126 let mut f = File::open(&file_path).expect(&format!(
127 "[rbatis-codegen] can't find file='{}',url='{}' ",
128 file_path.to_str().unwrap_or_default(),
129 url
130 ));
131 let mut html = String::new();
132 f.read_to_string(&mut html).expect("read fail");
133 let datas = load_mapper_vec(&html).expect("read fail");
134 let mut not_find = true;
135 for element in datas {
136 if element.tag.eq("sql") && element.attrs.get("id").eq(&Some(&ref_id)) {
137 x = element.clone();
138 not_find = false;
139 }
140 }
141 if not_find {
142 panic!(
143 "not find ref_id={} on file={}",
144 ref_id,
145 file_path.to_str().unwrap_or_default()
146 );
147 }
148 }
149 "current" => {
150 let mut ref_id_pair = ref_id.to_string();
151 for (k, v) in url.query_pairs() {
152 if k.eq("refid") {
153 ref_id_pair = v.to_string();
154 }
155 }
156 let element = sql_map
157 .get(ref_id_pair.as_str())
158 .expect(&format!(
159 "[rbatis-codegen] can not find element <include refid=\"{}\"> !",
160 ref_id
161 ))
162 .clone();
163 x = element;
164 }
165 _scheme => {
166 panic!("unimplemented scheme <include refid=\"{}\">", ref_id)
167 }
168 }
169 }
170 _ => match x.attrs.get("id") {
171 None => {}
172 Some(id) => {
173 if !id.is_empty() {
174 sql_map.insert(id.clone(), x.clone());
175 }
176 }
177 },
178 }
179 if x.childs.len() != 0 {
180 x.childs = include_replace(x.childs.clone(), sql_map);
181 }
182 results.push(x);
183 }
184 return results;
185}
186
187fn parse_html_node(
188 htmls: Vec<Element>,
189 ignore: &mut Vec<String>,
190 fn_name: &str,
191) -> proc_macro2::TokenStream {
192 let mut methods = quote!();
193 let fn_impl = parse(&htmls, &mut methods, ignore, fn_name);
194 let token = quote! {
195 #methods
196 #fn_impl
197 };
198 token
199}
200
201fn parse(
203 arg: &Vec<Element>,
204 methods: &mut proc_macro2::TokenStream,
205 ignore: &mut Vec<String>,
206 fn_name: &str,
207) -> proc_macro2::TokenStream {
208 let mut body = quote! {};
209 let fix_sql = quote! {};
210 for x in arg {
211 match x.tag.as_str() {
212 "mapper" => {
213 return parse(&x.childs, methods, ignore, fn_name);
214 }
215 "sql" => {
216 let code_sql = parse(&x.childs, methods, ignore, fn_name);
217 body = quote! {
218 #body
219 #code_sql
220 };
221 }
222 "include" => {
223 return parse(&x.childs, methods, ignore, fn_name);
224 }
225 "continue" => {
226 impl_continue(x, &mut body, ignore);
227 }
228 "break" => {
229 impl_break(x, &mut body, ignore);
230 }
231 "" => {
232 let mut string_data = remove_extra(&x.data);
233 let convert_list = find_convert_string(&string_data);
234 let mut formats_value = quote! {};
235 let mut replace_num = 0;
236 for (k, v) in convert_list {
237 let method_impl = crate::codegen::func::impl_fn(
238 &body.to_string(),
239 "",
240 &format!("\"{}\"", k),
241 false,
242 ignore,
243 );
244 if v.starts_with("#") {
245 string_data = string_data.replacen(&v, &"?", 1);
246 body = quote! {
247 #body
248 args.push(rbs::to_value(#method_impl).unwrap_or_default());
249 };
250 } else {
251 string_data = string_data.replacen(&v, &"{}", 1);
252 if formats_value.to_string().trim().ends_with(",") == false {
253 formats_value = quote!(#formats_value,);
254 }
255 formats_value = quote!(
256 #formats_value
257 &#method_impl.string()
258 );
259 replace_num += 1;
260 }
261 }
262 if !string_data.is_empty() {
263 if replace_num == 0 {
264 body = quote!(
265 #body
266 sql.push_str(#string_data);
267 );
268 } else {
269 body = quote!(
270 #body
271 sql.push_str(&format!(#string_data #formats_value));
272 );
273 }
274 }
275 }
276 "if" => {
277 let test_value = x
278 .attrs
279 .get("test")
280 .expect(&format!("{} element must be have test field!", x.tag));
281 let mut if_tag_body = quote! {};
282 if x.childs.len() != 0 {
283 if_tag_body = parse(&x.childs, methods, ignore, fn_name);
284 }
285 impl_if(
286 test_value,
287 if_tag_body,
288 &mut body,
289 methods,
290 quote! {},
291 ignore,
292 );
293 }
294 "trim" => {
295 let empty_string = String::new();
296 let prefix = x.attrs.get("prefix").unwrap_or(&empty_string).to_string();
297 let suffix = x.attrs.get("suffix").unwrap_or(&empty_string).to_string();
298 let prefix_overrides = x
299 .attrs
300 .get("start")
301 .unwrap_or_else(|| x.attrs.get("prefixOverrides").unwrap_or(&empty_string))
302 .to_string();
303 let suffix_overrides = x
304 .attrs
305 .get("end")
306 .unwrap_or_else(|| x.attrs.get("suffixOverrides").unwrap_or(&empty_string))
307 .to_string();
308 impl_trim(
309 &prefix,
310 &suffix,
311 &prefix_overrides,
312 &suffix_overrides,
313 x,
314 &mut body,
315 arg,
316 methods,
317 ignore,
318 fn_name,
319 );
320 }
321 "bind" => {
322 let name = x
323 .attrs
324 .get("name")
325 .expect("<bind> must be have name!")
326 .to_string();
327 let value = x
328 .attrs
329 .get("value")
330 .expect("<bind> element must be have value!")
331 .to_string();
332 let method_impl = crate::codegen::func::impl_fn(
333 &body.to_string(),
334 "",
335 &format!("\"{}\"", value),
336 false,
337 ignore,
338 );
339 let lit_str = LitStr::new(&name, Span::call_site());
340 body = quote! {
341 #body
342 if arg[#lit_str] == rbs::Value::Null{
344 arg.insert(rbs::Value::String(#lit_str.to_string()), rbs::Value::Null);
345 }
346 arg[#lit_str] = rbs::to_value(#method_impl).unwrap_or_default();
347 };
348 }
349
350 "where" => {
351 impl_trim(
352 " where ",
353 " ",
354 " |and |or ",
355 " | and| or",
356 x,
357 &mut body,
358 arg,
359 methods,
360 ignore,
361 fn_name,
362 );
363 body = quote! {
364 #body
365 sql = sql.trim_end_matches(" where ").to_string();
367 };
368 }
369
370 "choose" => {
371 let mut inner_body = quote! {};
372 for x in &x.childs {
373 if x.tag.ne("when") && x.tag.ne("otherwise") {
374 panic!("choose node's childs must be when node and otherwise node!");
375 }
376 if x.tag.eq("when") {
377 let test_value = x
378 .attrs
379 .get("test")
380 .expect(&format!("{} element must be have test field!", x.tag));
381 let mut if_tag_body = quote! {};
382 if x.childs.len() != 0 {
383 if_tag_body = parse(&x.childs, methods, ignore, fn_name);
384 }
385 impl_if(
386 test_value,
387 if_tag_body,
388 &mut inner_body,
389 methods,
390 quote! {return sql;},
391 ignore,
392 );
393 }
394 if x.tag.eq("otherwise") {
395 let child_body = parse(&x.childs, methods, ignore, fn_name);
396 impl_otherwise(child_body, &mut inner_body, methods, ignore);
397 }
398 }
399 let cup = x.child_string_cup() + 1000;
400 body = quote! {
401 #body
402 sql.push_str(&|| -> String {
403 let mut sql = String::with_capacity(#cup);
404 #inner_body
405 return sql;
406 }());
407 }
408 }
409
410 "foreach" => {
411 let empty_string = String::new();
412
413 let def_item = "item".to_string();
414 let def_index = "index".to_string();
415
416 let collection = x
417 .attrs
418 .get("collection")
419 .unwrap_or(&empty_string)
420 .to_string();
421 let mut item = x.attrs.get("item").unwrap_or(&def_item).to_string();
422 let mut findex = x.attrs.get("index").unwrap_or(&def_index).to_string();
423 let open = x.attrs.get("open").unwrap_or(&empty_string).to_string();
424 let close = x.attrs.get("close").unwrap_or(&empty_string).to_string();
425 let separator = x
426 .attrs
427 .get("separator")
428 .unwrap_or(&empty_string)
429 .to_string();
430
431 if item.is_empty() || item == "_" {
432 item = def_item;
433 }
434 if findex.is_empty() || findex == "_" {
435 findex = def_index;
436 }
437 let mut ignores = ignore.clone();
438 ignores.push(findex.to_string());
439 ignores.push(item.to_string());
440
441 let impl_body = parse(&x.childs, methods, &mut ignores, fn_name);
442 let method_impl = crate::codegen::func::impl_fn(
443 &body.to_string(),
444 "",
445 &format!("\"{}\"", collection),
446 false,
447 ignore,
448 );
449 body = quote! {
450 #body
451 };
452 let mut open_impl = quote! {};
453 if !open.is_empty() {
454 open_impl = quote! {
455 sql.push_str(#open);
456 };
457 }
458 let mut close_impl = quote! {};
459 if !close.is_empty() {
460 close_impl = quote! {sql.push_str(#close);};
461 }
462 let item_ident = Ident::new(&item, Span::call_site());
463 let index_ident = Ident::new(&findex, Span::call_site());
464 let mut split_code = quote! {};
465 let mut split_code_trim = quote! {};
466 if !separator.is_empty() {
467 split_code = quote! {
468 sql.push_str(#separator);
469 };
470 split_code_trim = quote! {
471 sql = sql.trim_end_matches(#separator).to_string();
472 };
473 }
474 body = quote! {
475 #body
476 #open_impl
477 for (ref #index_ident,#item_ident) in #method_impl {
478 #impl_body
479 #split_code
480 }
481 #split_code_trim
482 #close_impl
483 };
484 body = quote! {
485 #body
486 };
487 }
488
489 "set" => {
490 let collection = x.attrs.get("collection");
491 let skip_null = x.attrs.get("skip_null");
492 let skips = x.attrs.get("skips");
493 if let Some(collection) = collection {
494 let elements = make_sets(collection, skip_null, skips.unwrap_or(&"id".to_string()));
495 let code = parse(&elements, methods, ignore, fn_name);
496 body = quote! {
497 #body
498 #code
499 };
500 } else {
501 impl_trim(
502 " set ", " ", " |,", " |,", x, &mut body, arg, methods, ignore, fn_name,
503 );
504 }
505 }
506
507 "select" => {
508 let method_name = Ident::new(fn_name, Span::call_site());
509 let child_body = parse(&x.childs, methods, ignore, fn_name);
510 let cup = x.child_string_cup() + 1000;
511 let push_count = child_body.to_string().matches("args.push").count();
512 let select = quote! {
513 pub fn #method_name (mut arg: rbs::Value, _tag: char) -> (String,Vec<rbs::Value>) {
514 use rbatis_codegen::ops::*;
515 let mut sql = String::with_capacity(#cup);
516 let mut args = Vec::with_capacity(#push_count);
517 #child_body
518 #fix_sql
519 return (sql,args);
520 }
521 };
522 body = quote! {
523 #body
524 #select
525 };
526 }
527 "update" => {
528 let method_name = Ident::new(fn_name, Span::call_site());
529 let child_body = parse(&x.childs, methods, ignore, fn_name);
530 let cup = x.child_string_cup() + 1000;
531 let push_count = child_body.to_string().matches("args.push").count();
532 let select = quote! {
533 pub fn #method_name (mut arg: rbs::Value, _tag: char) -> (String,Vec<rbs::Value>) {
534 use rbatis_codegen::ops::*;
535 let mut sql = String::with_capacity(#cup);
536 let mut args = Vec::with_capacity(#push_count);
537 #child_body
538 #fix_sql
539 return (sql,args);
540 }
541 };
542 body = quote! {
543 #body
544 #select
545 };
546 }
547 "insert" => {
548 let method_name = Ident::new(fn_name, Span::call_site());
549 let child_body = parse(&x.childs, methods, ignore, fn_name);
550 let cup = x.child_string_cup() + 1000;
551 let push_count = child_body.to_string().matches("args.push").count();
552 let select = quote! {
553 pub fn #method_name (mut arg: rbs::Value, _tag: char) -> (String,Vec<rbs::Value>) {
554 use rbatis_codegen::ops::*;
555 let mut sql = String::with_capacity(#cup);
556 let mut args = Vec::with_capacity(#push_count);
557 #child_body
558 #fix_sql
559 return (sql,args);
560 }
561 };
562 body = quote! {
563 #body
564 #select
565 };
566 }
567 "delete" => {
568 let method_name = Ident::new(fn_name, Span::call_site());
569 let child_body = parse(&x.childs, methods, ignore, fn_name);
570 let cup = x.child_string_cup() + 1000;
571 let push_count = child_body.to_string().matches("args.push").count();
572 let select = quote! {
573 pub fn #method_name (mut arg: rbs::Value, _tag: char) -> (String,Vec<rbs::Value>) {
574 use rbatis_codegen::ops::*;
575 let mut sql = String::with_capacity(#cup);
576 let mut args = Vec::with_capacity(#push_count);
577 #child_body
578 #fix_sql
579 return (sql,args);
580 }
581 };
582 body = quote! {
583 #body
584 #select
585 };
586 }
587 _ => {}
588 }
589 }
590
591 body.into()
592}
593
594fn make_sets(collection: &str, skip_null: Option<&String>, skips: &str) -> Vec<Element> {
596 let mut is_skip_null = true;
597 if let Some(skip_null_value) = skip_null {
598 if skip_null_value.eq("false") {
599 is_skip_null = false;
600 }
601 }
602 let skip_strs: Vec<&str> = skips.split(',').collect();
603 let mut skip_element = vec![];
604 for x in skip_strs {
605 let element = Element {
606 tag: "if".to_string(),
607 data: "".to_string(),
608 attrs: {
609 let mut attr = HashMap::new();
610 attr.insert("test".to_string(), format!("k == '{}'", x.to_string()));
611 attr
612 },
613 childs: vec![Element {
614 tag: "continue".to_string(),
615 data: "".to_string(),
616 attrs: Default::default(),
617 childs: vec![],
618 }],
619 };
620 skip_element.push(element);
621 }
622 let mut for_each_body = vec![];
623 for x in skip_element {
624 for_each_body.push(x);
625 }
626 if is_skip_null {
627 for_each_body.push(Element {
628 tag: "if".to_string(),
629 data: "".to_string(),
630 attrs: {
631 let mut attr = HashMap::new();
632 attr.insert("test".to_string(), "v == null".to_string());
633 attr
634 },
635 childs: vec![
636 Element {
637 tag: "continue".to_string(),
638 data: "".to_string(),
639 attrs: Default::default(),
640 childs: vec![],
641 },
642 ],
643 });
644 }
645 for_each_body.push(Element {
646 tag: "".to_string(),
647 data: "${k}=#{v},".to_string(),
648 attrs: Default::default(),
649 childs: vec![],
650 });
651 let mut elements = vec![];
652 elements.push(Element {
653 tag: "trim".to_string(),
654 data: "".to_string(),
655 attrs: {
656 let mut attr = HashMap::new();
657 attr.insert("prefix".to_string(), " set ".to_string());
658 attr.insert("suffix".to_string(), " ".to_string());
659 attr.insert("start".to_string(), " ".to_string());
660 attr.insert("end".to_string(), " ".to_string());
661 attr
662 },
663 childs: vec![Element {
664 tag: "trim".to_string(),
665 data: "".to_string(),
666 attrs: {
667 let mut attr = HashMap::new();
668 attr.insert("prefix".to_string(), "".to_string());
669 attr.insert("suffix".to_string(), "".to_string());
670 attr.insert("start".to_string(), ",".to_string());
671 attr.insert("end".to_string(), ",".to_string());
672 attr
673 },
674 childs: vec![Element {
675 tag: "foreach".to_string(),
676 data: "".to_string(),
677 attrs: {
678 let mut attr = HashMap::new();
679 attr.insert("collection".to_string(), collection.to_string());
680 attr.insert("index".to_string(), "k".to_string());
681 attr.insert("item".to_string(), "v".to_string());
682 attr
683 },
684 childs: for_each_body,
685 }],
686 }],
687 });
688 elements
689}
690
691fn remove_extra(txt: &str) -> String {
692 let txt = txt.trim().replace("\\r", "");
693 let lines: Vec<&str> = txt.split("\n").collect();
694 let mut data = String::with_capacity(txt.len());
695 let mut index = 0;
696 for line in &lines {
697 let mut line = line.trim_start().trim_end();
698 if line.starts_with("`") {
699 line = line.trim_start_matches("`");
700 }
701 if line.ends_with("`") {
702 line = line.trim_end_matches("`");
703 }
704 data.push_str(line);
705 if index + 1 < lines.len() {
706 data.push_str("\n");
707 }
708 index += 1;
709 }
710 if data.starts_with("`") && data.ends_with("`") {
711 data = data
712 .trim_start_matches("`")
713 .trim_end_matches("`")
714 .to_string();
715 }
716 data = data.replace("``", "").to_string();
717 data
718}
719
720fn impl_continue(_x: &Element, body: &mut proc_macro2::TokenStream, _ignore: &mut Vec<String>) {
721 *body = quote! {
722 #body
723 continue
724 };
725}
726
727fn impl_break(_x: &Element, body: &mut proc_macro2::TokenStream, _ignore: &mut Vec<String>) {
728 *body = quote! {
729 #body
730 break
731 };
732}
733
734fn impl_if(
735 test_value: &str,
736 if_tag_body: proc_macro2::TokenStream,
737 body: &mut proc_macro2::TokenStream,
738 _methods: &mut proc_macro2::TokenStream,
739 appends: proc_macro2::TokenStream,
740 ignore: &mut Vec<String>,
741) {
742 let method_impl = crate::codegen::func::impl_fn(
743 &body.to_string(),
744 "",
745 &format!("\"{}\"", test_value),
746 false,
747 ignore,
748 );
749 *body = quote! {
750 #body
751 if #method_impl.to_owned().into() {
752 #if_tag_body
753 #appends
754 }
755 };
756}
757
758fn impl_otherwise(
759 child_body: proc_macro2::TokenStream,
760 body: &mut proc_macro2::TokenStream,
761 _methods: &mut proc_macro2::TokenStream,
762 _ignore: &mut Vec<String>,
763) {
764 *body = quote!(
765 #body
766 #child_body
767 );
768}
769
770fn impl_trim(
771 prefix: &str,
772 suffix: &str,
773 start: &str,
774 end: &str,
775 x: &Element,
776 body: &mut proc_macro2::TokenStream,
777 _arg: &Vec<Element>,
778 methods: &mut proc_macro2::TokenStream,
779 ignore: &mut Vec<String>,
780 fn_name: &str,
781) {
782 let trim_body = parse(&x.childs, methods, ignore, fn_name);
783 let prefixes: Vec<&str> = start.split("|").collect();
784 let suffixes: Vec<&str> = end.split("|").collect();
785 let have_trim = prefixes.len() != 0 && suffixes.len() != 0;
786 let cup = x.child_string_cup();
787 let mut trims = quote! {
788 let mut sql= String::with_capacity(#cup);
789 #trim_body
790 sql=sql
791 };
792 for x in prefixes {
793 trims = quote! {
794 #trims
795 .trim_start_matches(#x)
796 }
797 }
798 for x in suffixes {
799 trims = quote! {
800 #trims
801 .trim_end_matches(#x)
802 }
803 }
804 if !prefix.is_empty() {
805 *body = quote! {
806 #body
807 sql.push_str(#prefix);
808 };
809 }
810 if have_trim {
811 *body = quote! {
812 #body
813 sql.push_str(&{#trims.to_string(); sql });
814 };
815 }
816 if !suffix.is_empty() {
817 *body = quote! {
818 #body
819 sql.push_str(#suffix);
820 };
821 }
822}
823
824pub fn impl_fn_html(m: &ItemFn, args: &ParseArgs) -> TokenStream {
825 let fn_name = m.sig.ident.to_string();
826 if args.sqls.len() == 0 {
827 panic!(
828 "[rbatis-codegen] #[html_sql()] must have html_data, for example: {}",
829 stringify!(#[html_sql(r#"<select id="select_by_condition">`select * from biz_activity</select>"#)])
830 );
831 }
832 let html_data = args.sqls[0].to_token_stream().to_string();
833 let t = parse_html(&html_data, &fn_name, &mut vec![]);
834 return t.into();
835}