Skip to main content

qusql_sqlx_type_macro/
lib.rs

1//! Implementation of typed db query macros
2//!
3//! Used the exposed macros from the qusql-sqlx-type crate
4//!
5#![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
20/// Path where the sqlx-type-schema.sql file can be found
21static 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        /// Struct used to deserialize the cargo betadata
43        #[derive(Deserialize)]
44        struct CargoMetadata {
45            /// The root of the workspace
46            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
61/// Source of the sqlx-type-schema.sql
62// If we are in a workspace, lookup `workspace_root` since `CARGO_MANIFEST_DIR` won't
63// reflect the workspace dir: https://github.com/rust-lang/cargo/issues/3946
64static 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
74/// Construct a none color report for an issue
75fn 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
96/// Construct a color report for an issue
97fn 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
125/// Source name adaptor for ariadne
126struct 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
140/// Parsed version of the sqlx-type-schema.sql
141static 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
175/// Produce quoted arguments for a query
176fn 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
330/// Output an [Issue] as a compile error
331fn 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
350/// Construct row struct members, and fill in statements
351fn 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            // TODO error
402            //errors.push(syn::Error::new(span, String::from_utf8(out).unwrap()).to_compile_error().into());
403            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
419/// Parsed query! macro
420struct Query {
421    /// The query expression
422    query: String,
423    /// The span of the query expression
424    query_span: Span,
425    /// The arguments to supply to the query
426    args: Vec<Expr>,
427    /// The last span parsed
428    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/// Statically checked SQL query, similarly to sqlx::query!.
457///
458/// This expands to an instance of query::Map that outputs an ad-hoc anonymous struct type.
459#[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
680/// Fill in row values in a query_as struct
681fn 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            // TODO error
729            //errors.push(syn::Error::new(span, String::from_utf8(out).unwrap()).to_compile_error().into());
730            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
743/// Parse result of a query_as! macro
744struct QueryAs {
745    /// Name of output type
746    as_: Ident,
747    /// The query to execute
748    query: String,
749    /// The span of the query to execute
750    query_span: Span,
751    /// The arguments to supply
752    args: Vec<Expr>,
753    /// The last span parsed
754    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/// A variant of query! which takes a path to an explicitly defined struct as the output type.
788///
789/// This lets you return the struct from a function or add your own trait implementations.
790#[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            //println!("TOKENS: {}", s);
830            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}