1#![forbid(unsafe_code)]
6
7use std::ops::Deref;
8use std::path::PathBuf;
9
10use ariadne::{Color, Label, Report, ReportKind, Source};
11use once_cell::sync::Lazy;
12use proc_macro::TokenStream;
13use proc_macro2::Span;
14use quote::{format_ident, quote, quote_spanned};
15use qusql_type::schema::{parse_schemas, Schemas};
16use qusql_type::{type_statement, Issue, SQLArguments, SQLDialect, SelectTypeColumn, TypeOptions};
17use syn::spanned::Spanned;
18use syn::{parse::Parse, punctuated::Punctuated, Expr, Ident, LitStr, Token};
19
20static SCHEMA_PATH: Lazy<PathBuf> = Lazy::new(|| {
22 let mut schema_path: PathBuf = std::env::var("CARGO_MANIFEST_DIR")
23 .expect("`CARGO_schema_path` must be set")
24 .into();
25
26 schema_path.push("sqlx-type-schema.sql");
27
28 if !schema_path.exists() {
29 use serde::Deserialize;
30 use std::process::Command;
31
32 let cargo = std::env::var("CARGO").expect("`CARGO` must be set");
33 schema_path.pop();
34
35 let output = Command::new(cargo)
36 .args(["metadata", "--format-version=1"])
37 .current_dir(&schema_path)
38 .env_remove("__CARGO_FIX_PLZ")
39 .output()
40 .expect("Could not fetch metadata");
41
42 #[derive(Deserialize)]
44 struct CargoMetadata {
45 workspace_root: PathBuf,
47 }
48
49 let metadata: CargoMetadata =
50 serde_json::from_slice(&output.stdout).expect("Invalid `cargo metadata` output");
51
52 schema_path = metadata.workspace_root;
53 schema_path.push("sqlx-type-schema.sql");
54 }
55 if !schema_path.exists() {
56 panic!("Unable to locate sqlx-type-schema.sql");
57 }
58 schema_path
59});
60
61static SCHEMA_SRC: Lazy<String> =
65 Lazy::new(|| match std::fs::read_to_string(SCHEMA_PATH.as_path()) {
66 Ok(v) => v,
67 Err(e) => panic!(
68 "Unable to read schema from {:?}: {}",
69 SCHEMA_PATH.as_path(),
70 e
71 ),
72 });
73
74fn issue_to_report(issue: Issue) -> Report<'static, std::ops::Range<usize>> {
76 let mut builder = Report::build(
77 match issue.level {
78 qusql_type::Level::Warning => ReportKind::Warning,
79 qusql_type::Level::Error => ReportKind::Error,
80 },
81 issue.span.clone(),
82 )
83 .with_config(ariadne::Config::default().with_color(false))
84 .with_label(
85 Label::new(issue.span)
86 .with_order(-1)
87 .with_priority(-1)
88 .with_message(issue.message),
89 );
90 for frag in issue.fragments {
91 builder = builder.with_label(Label::new(frag.span).with_message(frag.message));
92 }
93 builder.finish()
94}
95
96fn issue_to_report_color(issue: Issue) -> Report<'static, std::ops::Range<usize>> {
98 let mut builder = Report::build(
99 match issue.level {
100 qusql_type::Level::Warning => ReportKind::Warning,
101 qusql_type::Level::Error => ReportKind::Error,
102 },
103 issue.span.clone(),
104 )
105 .with_label(
106 Label::new(issue.span)
107 .with_color(match issue.level {
108 qusql_type::Level::Warning => Color::Yellow,
109 qusql_type::Level::Error => Color::Red,
110 })
111 .with_order(-1)
112 .with_priority(-1)
113 .with_message(issue.message),
114 );
115 for frag in issue.fragments {
116 builder = builder.with_label(
117 Label::new(frag.span)
118 .with_color(Color::Blue)
119 .with_message(frag.message),
120 );
121 }
122 builder.finish()
123}
124
125struct NamedSource<'a>(&'a str, Source<&'a str>);
127
128impl<'a> ariadne::Cache<()> for &NamedSource<'a> {
129 type Storage = &'a str;
130
131 fn display<'b>(&self, _: &'b ()) -> Option<impl std::fmt::Display + 'b> {
132 Some(self.0.to_string())
133 }
134
135 fn fetch(&mut self, _: &()) -> Result<&Source<Self::Storage>, impl std::fmt::Debug> {
136 Ok::<_, ()>(&self.1)
137 }
138}
139
140static SCHEMAS: Lazy<(Schemas, SQLDialect)> = Lazy::new(|| {
142 let schema_src = SCHEMA_SRC.as_str();
143 let dialect = if let Some(first_line) = schema_src.lines().next() {
144 if first_line.contains("sql-product: postgres") {
145 SQLDialect::PostgreSQL
146 } else if first_line.contains("sql-product: sqlite") {
147 SQLDialect::Sqlite
148 } else {
149 SQLDialect::MariaDB
150 }
151 } else {
152 SQLDialect::MariaDB
153 };
154
155 let options = TypeOptions::new().dialect(dialect.clone());
156 let mut issues = qusql_type::Issues::new(schema_src);
157 let schemas = parse_schemas(schema_src, &mut issues, &options);
158 if !issues.is_ok() {
159 let source = NamedSource("sqlx-type-schema.sql", Source::from(schema_src));
160 let mut err = false;
161 for issue in issues.into_vec() {
162 if issue.level == qusql_type::Level::Error {
163 err = true;
164 }
165 let r = issue_to_report_color(issue);
166 r.eprint(&source).unwrap();
167 }
168 if err {
169 panic!("Errors processing sqlx-type-schema.sql");
170 }
171 }
172 (schemas, dialect)
173});
174
175fn quote_args(
177 errors: &mut Vec<proc_macro2::TokenStream>,
178 query: &str,
179 last_span: Span,
180 args: &[Expr],
181 arguments: &[(qusql_type::ArgumentKey<'_>, qusql_type::FullType)],
182 dialect: &SQLDialect,
183) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
184 let cls = match dialect {
185 SQLDialect::MariaDB => quote!(sqlx::mysql::MySql),
186 SQLDialect::Sqlite => quote!(sqlx::sqlite::Sqlite),
187 SQLDialect::PostgreSQL => quote!(sqlx::postgres::Postgres),
188 };
189
190 let mut at = Vec::new();
191 let inv = qusql_type::FullType::invalid();
192 for (k, v) in arguments {
193 match k {
194 qusql_type::ArgumentKey::Index(i) => {
195 while at.len() <= *i {
196 at.push(&inv);
197 }
198 at[*i] = v;
199 }
200 qusql_type::ArgumentKey::Identifier(_) => {
201 errors.push(
202 syn::Error::new(last_span.span(), "Named arguments not supported")
203 .to_compile_error(),
204 );
205 }
206 }
207 }
208
209 if at.len() > args.len() {
210 errors.push(
211 syn::Error::new(
212 last_span,
213 format!("Expected {} additional arguments", at.len() - args.len()),
214 )
215 .to_compile_error(),
216 );
217 }
218
219 if let Some(args) = args.get(at.len()..) {
220 for arg in args {
221 errors.push(syn::Error::new(arg.span(), "unexpected argument").to_compile_error());
222 }
223 }
224
225 let arg_names = (0..args.len())
226 .map(|i| format_ident!("arg{}", i))
227 .collect::<Vec<_>>();
228
229 let mut arg_bindings = Vec::new();
230 let mut arg_add = Vec::new();
231
232 let mut list_lengths = Vec::new();
233
234 for ((qa, ta), name) in args.iter().zip(at).zip(&arg_names) {
235 let mut t = match ta.t {
236 qusql_type::Type::U8 => quote! {u8},
237 qusql_type::Type::I8 => quote! {i8},
238 qusql_type::Type::U16 => quote! {u16},
239 qusql_type::Type::I16 => quote! {i16},
240 qusql_type::Type::U32 => quote! {u32},
241 qusql_type::Type::I32 => quote! {i32},
242 qusql_type::Type::U64 => quote! {u64},
243 qusql_type::Type::I64 => quote! {i64},
244 qusql_type::Type::Base(qusql_type::BaseType::Any) => quote! {qusql_sqlx_type::Any},
245 qusql_type::Type::Base(qusql_type::BaseType::Bool) => quote! {bool},
246 qusql_type::Type::Base(qusql_type::BaseType::Bytes) => quote! {&[u8]},
247 qusql_type::Type::Base(qusql_type::BaseType::Date) => quote! {qusql_sqlx_type::Date},
248 qusql_type::Type::Base(qusql_type::BaseType::DateTime) => {
249 quote! {qusql_sqlx_type::DateTime}
250 }
251 qusql_type::Type::Base(qusql_type::BaseType::Float) => quote! {qusql_sqlx_type::Float},
252 qusql_type::Type::Base(qusql_type::BaseType::Integer) => {
253 quote! {qusql_sqlx_type::Integer}
254 }
255 qusql_type::Type::Base(qusql_type::BaseType::String) => quote! {&str},
256 qusql_type::Type::Base(qusql_type::BaseType::Time) => quote! {qusql_sqlx_type::Time},
257 qusql_type::Type::Base(qusql_type::BaseType::TimeInterval) => todo!("time_interval"),
258 qusql_type::Type::Base(qusql_type::BaseType::TimeStamp) => {
259 quote! {qusql_sqlx_type::Timestamp}
260 }
261 qusql_type::Type::Null => todo!("null"),
262 qusql_type::Type::Invalid => quote! {std::convert::Infallible},
263 qusql_type::Type::Enum(_) => quote! {&str},
264 qusql_type::Type::Set(_) => quote! {&str},
265 qusql_type::Type::Args(_, _) => todo!("args"),
266 qusql_type::Type::F32 => quote! {f32},
267 qusql_type::Type::F64 => quote! {f64},
268 qusql_type::Type::JSON => quote! {qusql_sqlx_type::Any},
269 };
270 if !ta.not_null {
271 t = quote! {Option<#t>}
272 }
273 let span = qa.span();
274 if ta.list_hack {
275 list_lengths.push(quote!(#name.len()));
276 arg_bindings.push(quote_spanned! {span=>
277 let #name = &(#qa);
278 args_count += #name.len();
279 for v in #name.iter() {
280 size_hints += ::sqlx::encode::Encode::<#cls>::size_hint(v);
281 }
282 if false {
283 qusql_sqlx_type::check_arg_list_hack::<#t, _>(#name);
284 ::std::panic!();
285 }
286 });
287 arg_add.push(quote!(
288 for v in #name.iter() {
289 e = e.and_then(|()| query_args.add(v));
290 }
291 ));
292 } else {
293 arg_bindings.push(quote_spanned! {span=>
294 let #name = &(#qa);
295 args_count += 1;
296 size_hints += ::sqlx::encode::Encode::<#cls>::size_hint(#name);
297 if false {
298 qusql_sqlx_type::check_arg::<#t, _>(#name);
299 ::std::panic!();
300 }
301 });
302 arg_add.push(quote!(e = e.and_then(|()| query_args.add(#name));));
303 }
304 }
305
306 let query = if list_lengths.is_empty() {
307 quote!(#query)
308 } else {
309 quote!(
310 &qusql_sqlx_type::convert_list_query(#query, &[#(#list_lengths),*])
311 )
312 };
313
314 (
315 quote! {
316 let mut size_hints = 0;
317 let mut args_count = 0;
318 #(#arg_bindings)*
319
320 let mut query_args = <#cls as ::sqlx::database::Database>::Arguments::default();
321 query_args.reserve(args_count, size_hints);
322 let mut e = Ok(());
323 #(#arg_add)*
324 let query_args = e.and_then(|()| Ok(query_args));
325 },
326 query,
327 )
328}
329
330fn issues_to_errors(issues: Vec<Issue>, source: &str, span: Span) -> Vec<proc_macro2::TokenStream> {
332 if !issues.is_empty() {
333 let source = NamedSource("", Source::from(source));
334 let mut err = false;
335 let mut out = Vec::new();
336 for issue in issues {
337 if issue.level == qusql_type::Level::Error {
338 err = true;
339 }
340 let r = issue_to_report(issue);
341 r.write(&source, &mut out).unwrap();
342 }
343 if err {
344 return vec![syn::Error::new(span, String::from_utf8(out).unwrap()).to_compile_error()];
345 }
346 }
347 Vec::new()
348}
349
350fn construct_row(
352 columns: &[SelectTypeColumn],
353) -> (Vec<proc_macro2::TokenStream>, Vec<proc_macro2::TokenStream>) {
354 let mut row_members = Vec::new();
355 let mut row_construct = Vec::new();
356 for (i, c) in columns.iter().enumerate() {
357 let mut t = match c.type_.t {
358 qusql_type::Type::U8 => quote! {u8},
359 qusql_type::Type::I8 => quote! {i8},
360 qusql_type::Type::U16 => quote! {u16},
361 qusql_type::Type::I16 => quote! {i16},
362 qusql_type::Type::U32 => quote! {u32},
363 qusql_type::Type::I32 => quote! {i32},
364 qusql_type::Type::U64 => quote! {u64},
365 qusql_type::Type::I64 => quote! {i64},
366 qusql_type::Type::Base(qusql_type::BaseType::Any) => todo!("from_any"),
367 qusql_type::Type::Base(qusql_type::BaseType::Bool) => quote! {bool},
368 qusql_type::Type::Base(qusql_type::BaseType::Bytes) => quote! {Vec<u8>},
369 qusql_type::Type::Base(qusql_type::BaseType::Date) => quote! {chrono::NaiveDate},
370 qusql_type::Type::Base(qusql_type::BaseType::DateTime) => {
371 quote! {chrono::NaiveDateTime}
372 }
373 qusql_type::Type::Base(qusql_type::BaseType::Float) => quote! {f64},
374 qusql_type::Type::Base(qusql_type::BaseType::Integer) => quote! {i64},
375 qusql_type::Type::Base(qusql_type::BaseType::String) => quote! {String},
376 qusql_type::Type::Base(qusql_type::BaseType::Time) => todo!("from_time"),
377 qusql_type::Type::Base(qusql_type::BaseType::TimeInterval) => {
378 todo!("from_time_interval")
379 }
380 qusql_type::Type::Base(qusql_type::BaseType::TimeStamp) => {
381 quote! {sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>}
382 }
383 qusql_type::Type::Null => todo!("from_null"),
384 qusql_type::Type::Invalid => quote! {i64},
385 qusql_type::Type::Enum(_) => quote! {String},
386 qusql_type::Type::Set(_) => quote! {String},
387 qusql_type::Type::Args(_, _) => todo!("from_args"),
388 qusql_type::Type::F32 => quote! {f32},
389 qusql_type::Type::F64 => quote! {f64},
390 qusql_type::Type::JSON => quote! {String},
391 };
392 let name = match &c.name {
393 Some(v) => v,
394 None => continue,
395 };
396
397 let ident = String::from("r#") + name.value;
398 let ident: Ident = if let Ok(ident) = syn::parse_str(&ident) {
399 ident
400 } else {
401 continue;
404 };
405
406 if !c.type_.not_null {
407 t = quote! {Option<#t>};
408 }
409 row_members.push(quote! {
410 #ident : #t
411 });
412 row_construct.push(quote! {
413 #ident: sqlx::Row::get(&row, #i)
414 });
415 }
416 (row_members, row_construct)
417}
418
419struct Query {
421 query: String,
423 query_span: Span,
425 args: Vec<Expr>,
427 last_span: Span,
429}
430
431impl Parse for Query {
432 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
433 let query_ = Punctuated::<LitStr, Token![+]>::parse_separated_nonempty(input)?;
434 let query: String = query_.iter().map(LitStr::value).collect();
435 let query_span = query_.span();
436 let mut last_span = query_span;
437 let mut args = Vec::new();
438 while !input.is_empty() {
439 let _ = input.parse::<syn::token::Comma>()?;
440 if input.is_empty() {
441 break;
442 }
443 let arg = input.parse::<Expr>()?;
444 last_span = arg.span();
445 args.push(arg);
446 }
447 Ok(Self {
448 query,
449 query_span,
450 args,
451 last_span,
452 })
453 }
454}
455
456#[proc_macro]
460pub fn query(input: TokenStream) -> TokenStream {
461 let query = syn::parse_macro_input!(input as Query);
462 let (schemas, dialect) = SCHEMAS.deref();
463 let options = TypeOptions::new()
464 .dialect(dialect.clone())
465 .arguments(match &dialect {
466 SQLDialect::MariaDB => SQLArguments::QuestionMark,
467 SQLDialect::Sqlite => SQLArguments::QuestionMark,
468 SQLDialect::PostgreSQL => SQLArguments::Dollar,
469 })
470 .list_hack(true);
471 let mut issues = qusql_type::Issues::new(&query.query);
472 let stmt = type_statement(schemas, &query.query, &mut issues, &options);
473 let sp = SCHEMA_PATH.as_path().to_str().unwrap();
474
475 let mut errors = issues_to_errors(issues.into_vec(), &query.query, query.query_span);
476 match &stmt {
477 qusql_type::StatementType::Select { columns, arguments } => {
478 let (args_tokens, q) = quote_args(
479 &mut errors,
480 &query.query,
481 query.last_span,
482 &query.args,
483 arguments,
484 dialect,
485 );
486 let (row_members, row_construct) = construct_row(columns);
487 let s = quote! { {
488 use ::sqlx::Arguments as _;
489 let _ = std::include_bytes!(#sp);
490 #(#errors; )*
491 #args_tokens;
492
493 struct Row {
494 #(#row_members),*
495 };
496 sqlx::__query_with_result(#q, query_args).map(|row|
497 Row{
498 #(#row_construct),*
499 }
500 )
501 }};
502 s.into()
503 }
504 qusql_type::StatementType::Delete {
505 arguments,
506 returning,
507 } => {
508 let (args_tokens, q) = quote_args(
509 &mut errors,
510 &query.query,
511 query.last_span,
512 &query.args,
513 arguments,
514 dialect,
515 );
516 let s = match returning.as_ref() {
517 Some(returning) => {
518 let (row_members, row_construct) = construct_row(returning);
519 quote! { {
520 use ::sqlx::Arguments as _;
521 let _ = std::include_bytes!(#sp);
522 #(#errors; )*
523 #args_tokens
524
525 struct Row {
526 #(#row_members),*
527 };
528 sqlx::__query_with_result(#q, query_args).map(|row|
529 Row{
530 #(#row_construct),*
531 }
532 )
533 }}
534 }
535 None => quote! { {
536 use ::sqlx::Arguments as _;
537 #(#errors; )*
538 #args_tokens
539 sqlx::__query_with_result(#q, query_args)
540 }
541 },
542 };
543 s.into()
544 }
545 qusql_type::StatementType::Insert {
546 arguments,
547 returning,
548 ..
549 } => {
550 let (args_tokens, q) = quote_args(
551 &mut errors,
552 &query.query,
553 query.last_span,
554 &query.args,
555 arguments,
556 dialect,
557 );
558 let s = match returning.as_ref() {
559 Some(returning) => {
560 let (row_members, row_construct) = construct_row(returning);
561 quote! { {
562 use ::sqlx::Arguments as _;
563 let _ = std::include_bytes!(#sp);
564 #(#errors; )*
565 #args_tokens
566
567 struct Row {
568 #(#row_members),*
569 };
570 sqlx::__query_with_result(#q, query_args).map(|row|
571 Row{
572 #(#row_construct),*
573 }
574 )
575 }}
576 }
577 None => quote! { {
578 use ::sqlx::Arguments as _;
579 #(#errors; )*
580 #args_tokens
581 sqlx::__query_with_result(#q, query_args)
582 }
583 },
584 };
585 s.into()
586 }
587 qusql_type::StatementType::Update {
588 arguments,
589 returning,
590 } => {
591 let (args_tokens, q) = quote_args(
592 &mut errors,
593 &query.query,
594 query.last_span,
595 &query.args,
596 arguments,
597 dialect,
598 );
599
600 let s = match returning.as_ref() {
601 Some(returning) => {
602 let (row_members, row_construct) = construct_row(returning);
603 quote! { {
604 use ::sqlx::Arguments as _;
605 let _ = std::include_bytes!(#sp);
606 #(#errors; )*
607 #args_tokens
608
609 struct Row {
610 #(#row_members),*
611 };
612 sqlx::__query_with_result(#q, query_args).map(|row|
613 Row{
614 #(#row_construct),*
615 }
616 )
617 }}
618 }
619 None => quote! { {
620 use ::sqlx::Arguments as _;
621 #(#errors; )*
622 #args_tokens
623 sqlx::__query_with_result(#q, query_args)
624 }
625 },
626 };
627 s.into()
628 }
629 qusql_type::StatementType::Replace {
630 arguments,
631 returning,
632 } => {
633 let (args_tokens, q) = quote_args(
634 &mut errors,
635 &query.query,
636 query.last_span,
637 &query.args,
638 arguments,
639 dialect,
640 );
641 let s = match returning.as_ref() {
642 Some(returning) => {
643 let (row_members, row_construct) = construct_row(returning);
644 quote! { {
645 use ::sqlx::Arguments as _;
646 let _ = std::include_bytes!(#sp);
647 #(#errors; )*
648 #args_tokens
649
650 struct Row {
651 #(#row_members),*
652 };
653 sqlx::__query_with_result(#q, query_args).map(|row|
654 Row{
655 #(#row_construct),*
656 }
657 )
658 }}
659 }
660 None => quote! { {
661 use ::sqlx::Arguments as _;
662 #(#errors; )*
663 #args_tokens
664 sqlx::__query_with_result(#q, query_args)
665 }
666 },
667 };
668 s.into()
669 }
670 qusql_type::StatementType::Invalid => {
671 let s = quote! { {
672 #(#errors; )*;
673 todo!("Invalid")
674 }};
675 s.into()
676 }
677 }
678}
679
680fn construct_row2(columns: &[SelectTypeColumn]) -> Vec<proc_macro2::TokenStream> {
682 let mut row_construct = Vec::new();
683 for (i, c) in columns.iter().enumerate() {
684 let mut t = match c.type_.t {
685 qusql_type::Type::U8 => quote! {u8},
686 qusql_type::Type::I8 => quote! {i8},
687 qusql_type::Type::U16 => quote! {u16},
688 qusql_type::Type::I16 => quote! {i16},
689 qusql_type::Type::U32 => quote! {u32},
690 qusql_type::Type::I32 => quote! {i32},
691 qusql_type::Type::U64 => quote! {u64},
692 qusql_type::Type::I64 => quote! {i64},
693 qusql_type::Type::Base(qusql_type::BaseType::Any) => todo!("from_any"),
694 qusql_type::Type::Base(qusql_type::BaseType::Bool) => quote! {bool},
695 qusql_type::Type::Base(qusql_type::BaseType::Bytes) => quote! {Vec<u8>},
696 qusql_type::Type::Base(qusql_type::BaseType::Date) => quote! {chrono::NaiveDate},
697 qusql_type::Type::Base(qusql_type::BaseType::DateTime) => {
698 quote! {chrono::NaiveDateTime}
699 }
700 qusql_type::Type::Base(qusql_type::BaseType::Float) => quote! {f64},
701 qusql_type::Type::Base(qusql_type::BaseType::Integer) => quote! {i64},
702 qusql_type::Type::Base(qusql_type::BaseType::String) => quote! {String},
703 qusql_type::Type::Base(qusql_type::BaseType::Time) => todo!("from_time"),
704 qusql_type::Type::Base(qusql_type::BaseType::TimeInterval) => {
705 todo!("from_time_interval")
706 }
707 qusql_type::Type::Base(qusql_type::BaseType::TimeStamp) => {
708 quote! {sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>}
709 }
710 qusql_type::Type::Null => todo!("from_null"),
711 qusql_type::Type::Invalid => quote! {i64},
712 qusql_type::Type::Enum(_) => quote! {String},
713 qusql_type::Type::Set(_) => quote! {String},
714 qusql_type::Type::Args(_, _) => todo!("from_args"),
715 qusql_type::Type::F32 => quote! {f32},
716 qusql_type::Type::F64 => quote! {f64},
717 qusql_type::Type::JSON => quote! {String},
718 };
719 let name = match &c.name {
720 Some(v) => v,
721 None => continue,
722 };
723
724 let ident = String::from("r#") + name.value;
725 let ident: Ident = if let Ok(ident) = syn::parse_str(&ident) {
726 ident
727 } else {
728 continue;
731 };
732
733 if !c.type_.not_null {
734 t = quote! {Option<#t>};
735 }
736 row_construct.push(quote! {
737 #ident: qusql_sqlx_type::arg_out::<#t, _, #i>(sqlx::Row::get(&row, #i))
738 });
739 }
740 row_construct
741}
742
743struct QueryAs {
745 as_: Ident,
747 query: String,
749 query_span: Span,
751 args: Vec<Expr>,
753 last_span: Span,
755}
756
757impl Parse for QueryAs {
758 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
759 let as_ = input.parse::<Ident>()?;
760 let _ = input.parse::<syn::token::Comma>()?;
761
762 let query_ = Punctuated::<LitStr, Token![+]>::parse_separated_nonempty(input)?;
763 let query: String = query_.iter().map(LitStr::value).collect();
764 let query_span = query_.span();
765
766 let mut last_span = query_span;
767 let mut args = Vec::new();
768 while !input.is_empty() {
769 let _ = input.parse::<syn::token::Comma>()?;
770 if input.is_empty() {
771 break;
772 }
773 let arg = input.parse::<Expr>()?;
774 last_span = arg.span();
775 args.push(arg);
776 }
777 Ok(Self {
778 as_,
779 query,
780 query_span,
781 args,
782 last_span,
783 })
784 }
785}
786
787#[proc_macro]
791pub fn query_as(input: TokenStream) -> TokenStream {
792 let query_as = syn::parse_macro_input!(input as QueryAs);
793 let (schemas, dialect) = SCHEMAS.deref();
794 let options = TypeOptions::new()
795 .dialect(dialect.clone())
796 .arguments(match &dialect {
797 SQLDialect::MariaDB => SQLArguments::QuestionMark,
798 SQLDialect::Sqlite => SQLArguments::QuestionMark,
799 SQLDialect::PostgreSQL => SQLArguments::Dollar,
800 })
801 .list_hack(true);
802 let mut issues = qusql_type::Issues::new(&query_as.query);
803 let stmt = type_statement(schemas, &query_as.query, &mut issues, &options);
804
805 let mut errors = issues_to_errors(issues.into_vec(), &query_as.query, query_as.query_span);
806 match &stmt {
807 qusql_type::StatementType::Select { columns, arguments } => {
808 let (args_tokens, q) = quote_args(
809 &mut errors,
810 &query_as.query,
811 query_as.last_span,
812 &query_as.args,
813 arguments,
814 dialect,
815 );
816
817 let row_construct = construct_row2(columns);
818 let row = query_as.as_;
819 let s = quote! { {
820 use ::sqlx::Arguments as _;
821 #(#errors; )*
822 #args_tokens
823 sqlx::__query_with_result(#q, query_args).map(|row|
824 #row{
825 #(#row_construct),*
826 }
827 )
828 }};
829 s.into()
831 }
832 qusql_type::StatementType::Delete { .. } => {
833 errors.push(
834 syn::Error::new(query_as.query_span, "DELETE not support in query_as")
835 .to_compile_error(),
836 );
837 quote! { {
838 #(#errors; )*
839 todo!("delete")
840 }}
841 .into()
842 }
843 qusql_type::StatementType::Insert {
844 returning: None, ..
845 } => {
846 errors.push(
847 syn::Error::new(
848 query_as.query_span,
849 "INSERT without RETURNING not support in query_as",
850 )
851 .to_compile_error(),
852 );
853 quote! { {
854 #(#errors; )*
855 todo!("insert")
856 }}
857 .into()
858 }
859 qusql_type::StatementType::Insert {
860 arguments,
861 returning: Some(returning),
862 ..
863 } => {
864 let (args_tokens, q) = quote_args(
865 &mut errors,
866 &query_as.query,
867 query_as.last_span,
868 &query_as.args,
869 arguments,
870 dialect,
871 );
872
873 let row_construct = construct_row2(returning);
874 let row = query_as.as_;
875 let s = quote! { {
876 use ::sqlx::Arguments as _;
877 #(#errors; )*
878 #args_tokens
879 sqlx::__query_with_result(#q, query_args).map(|row|
880 #row{
881 #(#row_construct),*
882 }
883 )
884 }};
885 s.into()
886 }
887 qusql_type::StatementType::Update { .. } => {
888 errors.push(
889 syn::Error::new(query_as.query_span, "UPDATE not support in query_as")
890 .to_compile_error(),
891 );
892 quote! { {
893 #(#errors; )*
894 todo!("update")
895 }}
896 .into()
897 }
898 qusql_type::StatementType::Replace {
899 returning: None, ..
900 } => {
901 errors.push(
902 syn::Error::new(
903 query_as.query_span,
904 "REPLACE without RETURNING not support in query_as",
905 )
906 .to_compile_error(),
907 );
908 quote! { {
909 #(#errors; )*
910 todo!("replace")
911 }}
912 .into()
913 }
914 qusql_type::StatementType::Replace {
915 arguments,
916 returning: Some(returning),
917 ..
918 } => {
919 let (args_tokens, q) = quote_args(
920 &mut errors,
921 &query_as.query,
922 query_as.last_span,
923 &query_as.args,
924 arguments,
925 dialect,
926 );
927
928 let row_construct = construct_row2(returning);
929 let row = query_as.as_;
930 let s = quote! { {
931 use ::sqlx::Arguments as _;
932 #(#errors; )*
933 #args_tokens
934 sqlx::__query_with_result(#q, query_args).map(|row|
935 #row{
936 #(#row_construct),*
937 }
938 )
939 }};
940 s.into()
941 }
942 qusql_type::StatementType::Invalid => quote! { {
943 #(#errors; )*;
944 todo!("invalid")
945 }}
946 .into(),
947 }
948}