1#![forbid(unsafe_code)]
6
7use std::collections::HashMap;
8use std::path::PathBuf;
9use std::sync::{Arc, Mutex};
10use std::time::SystemTime;
11
12use ariadne::{Color, Label, Report, ReportKind, Source};
13use proc_macro::TokenStream;
14use proc_macro2::Span;
15use quote::{format_ident, quote, quote_spanned};
16use qusql_type::schema::{parse_schemas, Schemas};
17use qusql_type::{
18 type_statement, ByteToChar, Issue, SQLArguments, SQLDialect, SelectTypeColumn, TypeOptions,
19};
20use syn::spanned::Spanned;
21use syn::{parse::Parse, punctuated::Punctuated, Expr, Ident, LitStr, Token};
22use yoke::{Yoke, Yokeable};
23
24static RESOLVED_SCHEMA_PATHS: Mutex<Option<HashMap<PathBuf, PathBuf>>> = Mutex::new(None);
33
34fn resolve_schema_path() -> PathBuf {
39 let manifest_dir: PathBuf = std::env::var("CARGO_MANIFEST_DIR")
40 .expect("`CARGO_MANIFEST_DIR` must be set")
41 .into();
42
43 let mut cache_guard = RESOLVED_SCHEMA_PATHS
44 .lock()
45 .expect("resolved schema paths lock poisoned");
46 let cache = cache_guard.get_or_insert_with(HashMap::new);
47
48 if let Some(cached) = cache.get(&manifest_dir) {
49 return cached.clone();
50 }
51
52 let mut schema_path = manifest_dir.join("sqlx-type-schema.sql");
53
54 if !schema_path.exists() {
55 use serde::Deserialize;
56 use std::process::Command;
57
58 let cargo = std::env::var("CARGO").expect("`CARGO` must be set");
59
60 let output = Command::new(cargo)
61 .args(["metadata", "--format-version=1"])
62 .current_dir(&manifest_dir)
63 .env_remove("__CARGO_FIX_PLZ")
64 .output()
65 .expect("Could not fetch metadata");
66
67 #[derive(Deserialize)]
69 struct CargoMetadata {
70 workspace_root: PathBuf,
72 }
73
74 let metadata: CargoMetadata =
75 serde_json::from_slice(&output.stdout).expect("Invalid `cargo metadata` output");
76
77 schema_path = metadata.workspace_root.join("sqlx-type-schema.sql");
78 }
79 if !schema_path.exists() {
80 panic!("Unable to locate sqlx-type-schema.sql");
81 }
82
83 cache.insert(manifest_dir, schema_path.clone());
84 schema_path
85}
86
87fn issue_to_report(issue: Issue, b2c: &ByteToChar) -> Report<'static, std::ops::Range<usize>> {
89 let span = b2c.map_span(issue.span);
90 let kind = match issue.level {
91 qusql_type::Level::Warning => ReportKind::Warning,
92 qusql_type::Level::Error => ReportKind::Error,
93 };
94 let mut builder = Report::build(kind, span.clone())
95 .with_config(ariadne::Config::default().with_color(false))
96 .with_message(&issue.message)
97 .with_label(
98 Label::new(span)
99 .with_order(-1)
100 .with_priority(-1)
101 .with_message(issue.message),
102 );
103 for frag in issue.fragments {
104 builder =
105 builder.with_label(Label::new(b2c.map_span(frag.span)).with_message(frag.message));
106 }
107 if let Some(help) = issue.help {
108 builder = builder.with_help(help);
109 }
110 builder.finish()
111}
112
113fn issue_to_report_color(
115 issue: Issue,
116 b2c: &ByteToChar,
117) -> Report<'static, std::ops::Range<usize>> {
118 let span = b2c.map_span(issue.span);
119 let err_color = match issue.level {
120 qusql_type::Level::Warning => Color::Yellow,
121 qusql_type::Level::Error => Color::Red,
122 };
123 let mut builder = Report::build(
124 match issue.level {
125 qusql_type::Level::Warning => ReportKind::Warning,
126 qusql_type::Level::Error => ReportKind::Error,
127 },
128 span.clone(),
129 )
130 .with_config(ariadne::Config::default().with_compact(true))
131 .with_message(&issue.message)
132 .with_label(
133 Label::new(span)
134 .with_color(err_color)
135 .with_order(-1)
136 .with_priority(-1)
137 .with_message(issue.message),
138 );
139 for frag in issue.fragments {
140 builder = builder.with_label(
141 Label::new(b2c.map_span(frag.span))
142 .with_color(Color::Blue)
143 .with_message(frag.message),
144 );
145 }
146 if let Some(help) = issue.help {
147 builder = builder.with_help(help);
148 }
149 builder.finish()
150}
151
152struct NamedSource<'a>(&'a str, Source<&'a str>);
154
155impl<'a> ariadne::Cache<()> for &NamedSource<'a> {
156 type Storage = &'a str;
157
158 fn display<'b>(&self, _: &'b ()) -> Option<impl std::fmt::Display + 'b> {
159 Some(self.0.to_string())
160 }
161
162 fn fetch(&mut self, _: &()) -> Result<&Source<Self::Storage>, impl std::fmt::Debug> {
163 Ok::<_, ()>(&self.1)
164 }
165}
166
167#[derive(Yokeable)]
168struct SchemasYoke<'a>(Schemas<'a>);
170
171struct SchemaCacheEntry {
175 schemas: Yoke<SchemasYoke<'static>, String>,
177 dialect: SQLDialect,
179 path: PathBuf,
181 file_len: u64,
183 modified: SystemTime,
185 hash: u64,
187}
188
189static SCHEMA_CACHE: Mutex<Option<HashMap<PathBuf, Arc<SchemaCacheEntry>>>> = Mutex::new(None);
194
195fn get_schemas() -> Arc<SchemaCacheEntry> {
198 let path = resolve_schema_path();
199 let meta = std::fs::metadata(&path).unwrap_or_else(|e| panic!("Cannot stat {path:?}: {e}"));
200 let file_len = meta.len();
201 let modified = meta.modified().unwrap_or(SystemTime::UNIX_EPOCH);
202
203 let mut cache_guard = SCHEMA_CACHE.lock().expect("schema cache lock poisoned");
204 let cache = cache_guard.get_or_insert_with(HashMap::new);
205 if let Some(entry) = cache.get(&path) {
206 if entry.file_len == file_len && entry.modified == modified {
207 return Arc::clone(entry);
208 }
209 }
210
211 let src_string = std::fs::read_to_string(&path)
213 .unwrap_or_else(|e| panic!("Unable to read schema from {path:?}: {e}"));
214
215 let dialect = {
218 let header = src_string.lines().take(2).collect::<Vec<_>>().join(" ");
219 if header.contains("qusql-type-variant: postgis") || header.contains("sql-product: postgis")
220 {
221 SQLDialect::PostGIS
222 } else if header.contains("sql-product: postgres") {
223 SQLDialect::PostgreSQL
224 } else if header.contains("sql-product: sqlite") {
225 SQLDialect::Sqlite
226 } else {
227 SQLDialect::MariaDB
228 }
229 };
230
231 let dialect_for_closure = dialect.clone();
232
233 let schema_hash = {
235 use std::hash::{DefaultHasher, Hash, Hasher};
236 let mut hasher = DefaultHasher::new();
237 src_string.hash(&mut hasher);
238 hasher.finish()
239 };
240
241 let schemas = Yoke::attach_to_cart(src_string, move |src| {
242 let options = TypeOptions::new().dialect(dialect_for_closure);
243 let mut issues = qusql_type::Issues::new(src);
244 let parsed = parse_schemas(src, &mut issues, &options);
245 if !issues.is_ok() {
246 let b2c = ByteToChar::new(src.as_bytes());
247 let source = NamedSource("sqlx-type-schema.sql", Source::from(src));
248 let mut err = false;
249 for issue in issues.into_vec() {
250 if issue.level == qusql_type::Level::Error {
251 err = true;
252 }
253 issue_to_report_color(issue, &b2c).eprint(&source).unwrap();
254 }
255 if err {
256 panic!("Errors processing sqlx-type-schema.sql");
257 }
258 }
259 SchemasYoke(parsed)
260 });
261
262 let entry = Arc::new(SchemaCacheEntry {
263 schemas,
264 dialect,
265 path: path.clone(),
266 file_len,
267 modified,
268 hash: schema_hash,
269 });
270 cache.insert(path, Arc::clone(&entry));
271 entry
272}
273
274fn quote_args(
276 errors: &mut Vec<proc_macro2::TokenStream>,
277 query: &str,
278 last_span: Span,
279 args: &[Expr],
280 arguments: &[(qusql_type::ArgumentKey<'_>, qusql_type::FullType)],
281 dialect: &SQLDialect,
282) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
283 let cls = match dialect {
284 SQLDialect::MariaDB => quote!(sqlx::mysql::MySql),
285 SQLDialect::Sqlite => quote!(sqlx::sqlite::Sqlite),
286 SQLDialect::PostgreSQL | SQLDialect::PostGIS => quote!(sqlx::postgres::Postgres),
287 };
288
289 let mut at = Vec::new();
290 let inv = qusql_type::FullType::invalid();
291 for (k, v) in arguments {
292 match k {
293 qusql_type::ArgumentKey::Index(i) => {
294 while at.len() <= *i {
295 at.push(&inv);
296 }
297 at[*i] = v;
298 }
299 qusql_type::ArgumentKey::Identifier(_) => {
300 errors.push(
301 syn::Error::new(last_span.span(), "Named arguments not supported")
302 .to_compile_error(),
303 );
304 }
305 }
306 }
307
308 if at.len() > args.len() {
309 errors.push(
310 syn::Error::new(
311 last_span,
312 format!("Expected {} additional arguments", at.len() - args.len()),
313 )
314 .to_compile_error(),
315 );
316 }
317
318 if let Some(args) = args.get(at.len()..) {
319 for arg in args {
320 errors.push(syn::Error::new(arg.span(), "unexpected argument").to_compile_error());
321 }
322 }
323
324 let arg_names = (0..args.len())
325 .map(|i| format_ident!("arg{}", i))
326 .collect::<Vec<_>>();
327
328 let mut arg_bindings = Vec::new();
329 let mut arg_add = Vec::new();
330
331 let mut list_lengths = Vec::new();
332
333 for ((qa, ta), name) in args.iter().zip(at).zip(&arg_names) {
334 let mut t = match ta.t {
335 qusql_type::Type::U8 => quote! {u8},
336 qusql_type::Type::I8 => quote! {i8},
337 qusql_type::Type::U16 => quote! {u16},
338 qusql_type::Type::I16 => quote! {i16},
339 qusql_type::Type::U24 => quote! {u32},
340 qusql_type::Type::I24 => quote! {i32},
341 qusql_type::Type::U32 => quote! {u32},
342 qusql_type::Type::I32 => quote! {i32},
343 qusql_type::Type::U64 => quote! {u64},
344 qusql_type::Type::I64 => quote! {i64},
345 qusql_type::Type::Base(qusql_type::BaseType::Any) => quote! {qusql_sqlx_type::Any},
346 qusql_type::Type::Base(qusql_type::BaseType::Bool) => quote! {bool},
347 qusql_type::Type::Base(qusql_type::BaseType::Bytes) => quote! {&[u8]},
348 qusql_type::Type::Base(qusql_type::BaseType::Date) => quote! {qusql_sqlx_type::Date},
349 qusql_type::Type::Base(qusql_type::BaseType::DateTime) => {
350 quote! {qusql_sqlx_type::DateTime}
351 }
352 qusql_type::Type::Base(qusql_type::BaseType::Float) => quote! {qusql_sqlx_type::Float},
353 qusql_type::Type::Base(qusql_type::BaseType::Integer) => {
354 quote! {qusql_sqlx_type::Integer}
355 }
356 qusql_type::Type::Base(qusql_type::BaseType::String) => quote! {&str},
357 qusql_type::Type::Base(qusql_type::BaseType::Time) => quote! {qusql_sqlx_type::Time},
358 qusql_type::Type::Base(qusql_type::BaseType::TimeInterval) => todo!("time_interval"),
359 qusql_type::Type::Base(qusql_type::BaseType::TimeStamp) => {
360 quote! {qusql_sqlx_type::Timestamp}
361 }
362 qusql_type::Type::Base(qusql_type::BaseType::Uuid) => quote! {qusql_sqlx_type::Uuid},
363 qusql_type::Type::Null => todo!("null"),
364 qusql_type::Type::Invalid => quote! {std::convert::Infallible},
365 qusql_type::Type::Enum(_) => quote! {&str},
366 qusql_type::Type::Set(_) => quote! {&str},
367 qusql_type::Type::Args(_, _) => todo!("args"),
368 qusql_type::Type::F32 => quote! {f32},
369 qusql_type::Type::F64 => quote! {f64},
370 qusql_type::Type::JSON => quote! {qusql_sqlx_type::Any},
371 qusql_type::Type::Geometry => quote! {qusql_sqlx_type::Any},
372 qusql_type::Type::Range(_) => quote! {qusql_sqlx_type::Any},
373 qusql_type::Type::Array(_) => quote! {qusql_sqlx_type::Any},
374 };
375 if !ta.not_null {
376 t = quote! {Option<#t>}
377 }
378 let span = qa.span();
379 if ta.list_hack {
380 list_lengths.push(quote!(#name.len()));
381 arg_bindings.push(quote_spanned! {span=>
382 let #name = &(#qa);
383 args_count += #name.len();
384 for v in #name.iter() {
385 size_hints += ::sqlx::encode::Encode::<#cls>::size_hint(v);
386 }
387 if false {
388 qusql_sqlx_type::check_arg_list_hack::<#t, _>(#name);
389 ::std::panic!();
390 }
391 });
392 arg_add.push(quote!(
393 for v in #name.iter() {
394 e = e.and_then(|()| query_args.add(v));
395 }
396 ));
397 } else {
398 arg_bindings.push(quote_spanned! {span=>
399 let #name = &(#qa);
400 args_count += 1;
401 size_hints += ::sqlx::encode::Encode::<#cls>::size_hint(#name);
402 if false {
403 qusql_sqlx_type::check_arg::<#t, _>(#name);
404 ::std::panic!();
405 }
406 });
407 arg_add.push(quote!(e = e.and_then(|()| query_args.add(#name));));
408 }
409 }
410
411 let query = if list_lengths.is_empty() {
412 quote!(#query)
413 } else {
414 quote!(
415 &qusql_sqlx_type::convert_list_query(#query, &[#(#list_lengths),*])
416 )
417 };
418
419 (
420 quote! {
421 let mut size_hints = 0;
422 let mut args_count = 0;
423 #(#arg_bindings)*
424
425 let mut query_args = <#cls as ::sqlx::database::Database>::Arguments::default();
426 query_args.reserve(args_count, size_hints);
427 let mut e = Ok(());
428 #(#arg_add)*
429 let query_args = e.and_then(|()| Ok(query_args));
430 },
431 query,
432 )
433}
434
435fn issues_to_errors(issues: Vec<Issue>, source: &str, span: Span) -> Vec<proc_macro2::TokenStream> {
437 if !issues.is_empty() {
438 let b2c = ByteToChar::new(source.as_bytes());
439 let source = NamedSource("query", Source::from(source));
440 let mut err = false;
441 let mut out = Vec::new();
442 for issue in issues {
443 if issue.level == qusql_type::Level::Error {
444 err = true;
445 }
446 let r = issue_to_report(issue, &b2c);
447 r.write(&source, &mut out).unwrap();
448 }
449 if err {
450 let raw = String::from_utf8(out).unwrap();
451 let body = raw
454 .find('\n')
455 .map(|i| raw[i + 1..].trim_start_matches('\n').trim_end())
456 .unwrap_or(raw.trim_end());
457 return vec![syn::Error::new(span, body).to_compile_error()];
458 }
459 }
460 Vec::new()
461}
462
463fn construct_row(
465 columns: &[SelectTypeColumn],
466 is_postgres: bool,
467) -> (Vec<proc_macro2::TokenStream>, Vec<proc_macro2::TokenStream>) {
468 let mut row_members = Vec::new();
469 let mut row_construct = Vec::new();
470 for (i, c) in columns.iter().enumerate() {
471 let mut t = match c.type_.t {
472 qusql_type::Type::U8 => quote! {u8},
473 qusql_type::Type::I8 => quote! {i8},
474 qusql_type::Type::U16 => quote! {u16},
475 qusql_type::Type::I16 => quote! {i16},
476 qusql_type::Type::U24 => quote! {u32},
477 qusql_type::Type::I24 => quote! {i32},
478 qusql_type::Type::U32 => quote! {u32},
479 qusql_type::Type::I32 => quote! {i32},
480 qusql_type::Type::U64 => quote! {u64},
481 qusql_type::Type::I64 => quote! {i64},
482 qusql_type::Type::Base(qusql_type::BaseType::Any) => todo!("from_any"),
483 qusql_type::Type::Base(qusql_type::BaseType::Bool) => quote! {bool},
484 qusql_type::Type::Base(qusql_type::BaseType::Bytes) => quote! {Vec<u8>},
485 qusql_type::Type::Base(qusql_type::BaseType::Date) => quote! {chrono::NaiveDate},
486 qusql_type::Type::Base(qusql_type::BaseType::DateTime) => {
487 quote! {chrono::NaiveDateTime}
488 }
489 qusql_type::Type::Base(qusql_type::BaseType::Float) => quote! {f64},
490 qusql_type::Type::Base(qusql_type::BaseType::Integer) => {
491 if is_postgres {
492 quote! {i32}
493 } else {
494 quote! {i64}
495 }
496 }
497 qusql_type::Type::Base(qusql_type::BaseType::String) => quote! {String},
498 qusql_type::Type::Base(qusql_type::BaseType::Time) => todo!("from_time"),
499 qusql_type::Type::Base(qusql_type::BaseType::TimeInterval) => {
500 todo!("from_time_interval")
501 }
502 qusql_type::Type::Base(qusql_type::BaseType::TimeStamp) => {
503 quote! {sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>}
504 }
505 qusql_type::Type::Base(qusql_type::BaseType::Uuid) => {
506 quote! {qusql_sqlx_type::UuidValue}
507 }
508 qusql_type::Type::Null => todo!("from_null"),
509 qusql_type::Type::Invalid => quote! {i64},
510 qusql_type::Type::Enum(_) => quote! {String},
511 qusql_type::Type::Set(_) => quote! {String},
512 qusql_type::Type::Args(_, _) => todo!("from_args"),
513 qusql_type::Type::F32 => quote! {f32},
514 qusql_type::Type::F64 => quote! {f64},
515 qusql_type::Type::JSON => quote! {String},
516 qusql_type::Type::Geometry => quote! {Vec<u8>},
517 qusql_type::Type::Range(_) => quote! {Vec<u8>},
518 qusql_type::Type::Array(_) => quote! {qusql_sqlx_type::Any},
519 };
520 let name = match &c.name {
521 Some(v) => v,
522 None => continue,
523 };
524
525 let name_str = name.value;
527 let (name_str, force_not_null) = if let Some(stripped) = name_str.strip_suffix('!') {
528 (stripped, true)
529 } else {
530 (name_str, false)
531 };
532
533 let ident = String::from("r#") + name_str;
534 let ident: Ident = if let Ok(ident) = syn::parse_str(&ident) {
535 ident
536 } else {
537 continue;
540 };
541
542 if !c.type_.not_null && !force_not_null {
543 t = quote! {Option<#t>};
544 }
545 row_members.push(quote! {
546 #ident : #t
547 });
548 row_construct.push(quote! {
549 #ident: sqlx::Row::get(&row, #i)
550 });
551 }
552 (row_members, row_construct)
553}
554
555struct Query {
557 query: String,
559 query_span: Span,
561 args: Vec<Expr>,
563 last_span: Span,
565}
566
567impl Parse for Query {
568 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
569 let query_ = Punctuated::<LitStr, Token![+]>::parse_separated_nonempty(input)?;
570 let query: String = query_.iter().map(LitStr::value).collect();
571 let query_span = query_.span();
572 let mut last_span = query_span;
573 let mut args = Vec::new();
574 while !input.is_empty() {
575 let _ = input.parse::<syn::token::Comma>()?;
576 if input.is_empty() {
577 break;
578 }
579 let arg = input.parse::<Expr>()?;
580 last_span = arg.span();
581 args.push(arg);
582 }
583 Ok(Self {
584 query,
585 query_span,
586 args,
587 last_span,
588 })
589 }
590}
591
592#[proc_macro]
596pub fn query(input: TokenStream) -> TokenStream {
597 let query = syn::parse_macro_input!(input as Query);
598 let cache = get_schemas();
599 let (schemas, dialect, schema_hash) = (cache.schemas.get(), &cache.dialect, cache.hash);
600 let options = TypeOptions::new()
601 .dialect(dialect.clone())
602 .arguments(match &dialect {
603 SQLDialect::MariaDB => SQLArguments::QuestionMark,
604 SQLDialect::Sqlite => SQLArguments::QuestionMark,
605 SQLDialect::PostgreSQL | SQLDialect::PostGIS => SQLArguments::Dollar,
606 })
607 .list_hack(true);
608 let mut issues = qusql_type::Issues::new(&query.query);
609 let stmt = type_statement(&schemas.0, &query.query, &mut issues, &options);
610 let sp = cache.path.to_str().unwrap();
611 let mut errors = issues_to_errors(issues.into_vec(), &query.query, query.query_span);
612 match &stmt {
613 qusql_type::StatementType::Select { columns, arguments } => {
614 let (args_tokens, q) = quote_args(
615 &mut errors,
616 &query.query,
617 query.last_span,
618 &query.args,
619 arguments,
620 dialect,
621 );
622 let (row_members, row_construct) = construct_row(columns, dialect.is_postgresql());
623 let s = quote! { {
624 use ::sqlx::Arguments as _;
625 let _ = std::include_bytes!(#sp);
626 #(#errors; )*
627 #args_tokens;
628
629 struct Row {
630 #(#row_members),*
631 };
632 sqlx::__query_with_result(#q, query_args).map(|row|
633 Row{
634 #(#row_construct),*
635 }
636 )
637 }};
638 s.into()
639 }
640 qusql_type::StatementType::Delete {
641 arguments,
642 returning,
643 } => {
644 let (args_tokens, q) = quote_args(
645 &mut errors,
646 &query.query,
647 query.last_span,
648 &query.args,
649 arguments,
650 dialect,
651 );
652 let s = match returning.as_ref() {
653 Some(returning) => {
654 let (row_members, row_construct) =
655 construct_row(returning, dialect.is_postgresql());
656 quote! { {
657 use ::sqlx::Arguments as _;
658 let _ = std::include_bytes!(#sp);
659 #(#errors; )*
660 #args_tokens
661
662 struct Row {
663 #(#row_members),*
664 };
665 sqlx::__query_with_result(#q, query_args).map(|row|
666 Row{
667 #(#row_construct),*
668 }
669 )
670 }}
671 }
672 None => quote! { {
673 use ::sqlx::Arguments as _;
674 const _SCHEMA_HASH: u64 = #schema_hash;
675 #(#errors; )*
676 #args_tokens
677 sqlx::__query_with_result(#q, query_args)
678 }
679 },
680 };
681 s.into()
682 }
683 qusql_type::StatementType::Insert {
684 arguments,
685 returning,
686 ..
687 } => {
688 let (args_tokens, q) = quote_args(
689 &mut errors,
690 &query.query,
691 query.last_span,
692 &query.args,
693 arguments,
694 dialect,
695 );
696 let s = match returning.as_ref() {
697 Some(returning) => {
698 let (row_members, row_construct) =
699 construct_row(returning, dialect.is_postgresql());
700 quote! { {
701 use ::sqlx::Arguments as _;
702 let _ = std::include_bytes!(#sp);
703 #(#errors; )*
704 #args_tokens
705
706 struct Row {
707 #(#row_members),*
708 };
709 sqlx::__query_with_result(#q, query_args).map(|row|
710 Row{
711 #(#row_construct),*
712 }
713 )
714 }}
715 }
716 None => quote! { {
717 use ::sqlx::Arguments as _;
718 const _SCHEMA_HASH: u64 = #schema_hash;
719 #(#errors; )*
720 #args_tokens
721 sqlx::__query_with_result(#q, query_args)
722 }
723 },
724 };
725 s.into()
726 }
727 qusql_type::StatementType::Update {
728 arguments,
729 returning,
730 } => {
731 let (args_tokens, q) = quote_args(
732 &mut errors,
733 &query.query,
734 query.last_span,
735 &query.args,
736 arguments,
737 dialect,
738 );
739
740 let s = match returning.as_ref() {
741 Some(returning) => {
742 let (row_members, row_construct) =
743 construct_row(returning, dialect.is_postgresql());
744 quote! { {
745 use ::sqlx::Arguments as _;
746 let _ = std::include_bytes!(#sp);
747 #(#errors; )*
748 #args_tokens
749
750 struct Row {
751 #(#row_members),*
752 };
753 sqlx::__query_with_result(#q, query_args).map(|row|
754 Row{
755 #(#row_construct),*
756 }
757 )
758 }}
759 }
760 None => quote! { {
761 use ::sqlx::Arguments as _;
762 const _SCHEMA_HASH: u64 = #schema_hash;
763 #(#errors; )*
764 #args_tokens
765 sqlx::__query_with_result(#q, query_args)
766 }
767 },
768 };
769 s.into()
770 }
771 qusql_type::StatementType::Replace {
772 arguments,
773 returning,
774 } => {
775 let (args_tokens, q) = quote_args(
776 &mut errors,
777 &query.query,
778 query.last_span,
779 &query.args,
780 arguments,
781 dialect,
782 );
783 let s = match returning.as_ref() {
784 Some(returning) => {
785 let (row_members, row_construct) =
786 construct_row(returning, dialect.is_postgresql());
787 quote! { {
788 use ::sqlx::Arguments as _;
789 const _SCHEMA_HASH: u64 = #schema_hash;
790 let _ = std::include_bytes!(#sp);
791 #(#errors; )*
792 #args_tokens
793
794 struct Row {
795 #(#row_members),*
796 };
797 sqlx::__query_with_result(#q, query_args).map(|row|
798 Row{
799 #(#row_construct),*
800 }
801 )
802 }}
803 }
804 None => quote! { {
805 use ::sqlx::Arguments as _;
806 #(#errors; )*
807 #args_tokens
808 sqlx::__query_with_result(#q, query_args)
809 }
810 },
811 };
812 s.into()
813 }
814 qusql_type::StatementType::Truncate => {
815 errors.push(
816 syn::Error::new(query.query_span, "TRUNCATE not supported in query!")
817 .to_compile_error(),
818 );
819 quote! { {
820 #(#errors; )*
821 todo!("truncate")
822 }}
823 .into()
824 }
825 qusql_type::StatementType::Call { .. } => {
826 errors.push(
827 syn::Error::new(query.query_span, "CALL not supported in query!")
828 .to_compile_error(),
829 );
830 quote! { {
831 #(#errors; )*
832 todo!("call")
833 }}
834 .into()
835 }
836 qusql_type::StatementType::Transaction => {
837 errors.push(
838 syn::Error::new(
839 query.query_span,
840 "Transaction control not supported in query!",
841 )
842 .to_compile_error(),
843 );
844 quote! { {
845 #(#errors; )*
846 todo!("transaction")
847 }}
848 .into()
849 }
850 qusql_type::StatementType::Set => {
851 errors.push(
852 syn::Error::new(query.query_span, "SET not supported in query!").to_compile_error(),
853 );
854 quote! { {
855 #(#errors; )*
856 todo!("set")
857 }}
858 .into()
859 }
860 qusql_type::StatementType::Lock => {
861 errors.push(
862 syn::Error::new(query.query_span, "LOCK not supported in query!")
863 .to_compile_error(),
864 );
865 quote! { {
866 #(#errors; )*
867 todo!("lock")
868 }}
869 .into()
870 }
871 qusql_type::StatementType::Invalid => {
872 let s = quote! { {
873 #(#errors; )*;
874 todo!("Invalid")
875 }};
876 s.into()
877 }
878 }
879}
880
881fn construct_row2(
883 columns: &[SelectTypeColumn],
884 is_postgres: bool,
885) -> Vec<proc_macro2::TokenStream> {
886 let mut row_construct = Vec::new();
887 for (i, c) in columns.iter().enumerate() {
888 let mut t = match c.type_.t {
889 qusql_type::Type::U8 => quote! {u8},
890 qusql_type::Type::I8 => quote! {i8},
891 qusql_type::Type::U16 => quote! {u16},
892 qusql_type::Type::I16 => quote! {i16},
893 qusql_type::Type::U24 => quote! {u32},
894 qusql_type::Type::I24 => quote! {i32},
895 qusql_type::Type::U32 => quote! {u32},
896 qusql_type::Type::I32 => quote! {i32},
897 qusql_type::Type::U64 => quote! {u64},
898 qusql_type::Type::I64 => quote! {i64},
899 qusql_type::Type::Base(qusql_type::BaseType::Any) => todo!("from_any"),
900 qusql_type::Type::Base(qusql_type::BaseType::Bool) => quote! {bool},
901 qusql_type::Type::Base(qusql_type::BaseType::Bytes) => quote! {Vec<u8>},
902 qusql_type::Type::Base(qusql_type::BaseType::Date) => quote! {chrono::NaiveDate},
903 qusql_type::Type::Base(qusql_type::BaseType::DateTime) => {
904 quote! {chrono::NaiveDateTime}
905 }
906 qusql_type::Type::Base(qusql_type::BaseType::Float) => quote! {f64},
907 qusql_type::Type::Base(qusql_type::BaseType::Integer) => {
908 if is_postgres {
909 quote! {i32}
910 } else {
911 quote! {i64}
912 }
913 }
914 qusql_type::Type::Base(qusql_type::BaseType::String) => quote! {String},
915 qusql_type::Type::Base(qusql_type::BaseType::Time) => todo!("from_time"),
916 qusql_type::Type::Base(qusql_type::BaseType::TimeInterval) => {
917 todo!("from_time_interval")
918 }
919 qusql_type::Type::Base(qusql_type::BaseType::TimeStamp) => {
920 quote! {sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>}
921 }
922 qusql_type::Type::Base(qusql_type::BaseType::Uuid) => {
923 quote! {qusql_sqlx_type::UuidValue}
924 }
925 qusql_type::Type::Null => todo!("from_null"),
926 qusql_type::Type::Invalid => quote! {i64},
927 qusql_type::Type::Enum(_) => quote! {String},
928 qusql_type::Type::Set(_) => quote! {String},
929 qusql_type::Type::Args(_, _) => todo!("from_args"),
930 qusql_type::Type::F32 => quote! {f32},
931 qusql_type::Type::F64 => quote! {f64},
932 qusql_type::Type::JSON => quote! {String},
933 qusql_type::Type::Geometry => quote! {Vec<u8>},
934 qusql_type::Type::Range(_) => quote! {Vec<u8>},
935 qusql_type::Type::Array(_) => quote! {qusql_sqlx_type::Any},
936 };
937 let name = match &c.name {
938 Some(v) => v,
939 None => continue,
940 };
941
942 let name_str = name.value;
944 let (name_str, force_not_null) = if let Some(stripped) = name_str.strip_suffix('!') {
945 (stripped, true)
946 } else {
947 (name_str, false)
948 };
949
950 let ident = String::from("r#") + name_str;
951 let ident: Ident = if let Ok(ident) = syn::parse_str(&ident) {
952 ident
953 } else {
954 continue;
957 };
958
959 if !c.type_.not_null && !force_not_null {
960 t = quote! {Option<#t>};
961 }
962 row_construct.push(quote! {
963 #ident: qusql_sqlx_type::arg_out::<#t, _, #i>(sqlx::Row::get(&row, #i))
964 });
965 }
966 row_construct
967}
968
969struct QueryAs {
971 as_: Ident,
973 query: String,
975 query_span: Span,
977 args: Vec<Expr>,
979 last_span: Span,
981}
982
983impl Parse for QueryAs {
984 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
985 let as_ = input.parse::<Ident>()?;
986 let _ = input.parse::<syn::token::Comma>()?;
987
988 let query_ = Punctuated::<LitStr, Token![+]>::parse_separated_nonempty(input)?;
989 let query: String = query_.iter().map(LitStr::value).collect();
990 let query_span = query_.span();
991
992 let mut last_span = query_span;
993 let mut args = Vec::new();
994 while !input.is_empty() {
995 let _ = input.parse::<syn::token::Comma>()?;
996 if input.is_empty() {
997 break;
998 }
999 let arg = input.parse::<Expr>()?;
1000 last_span = arg.span();
1001 args.push(arg);
1002 }
1003 Ok(Self {
1004 as_,
1005 query,
1006 query_span,
1007 args,
1008 last_span,
1009 })
1010 }
1011}
1012
1013#[proc_macro]
1017pub fn query_as(input: TokenStream) -> TokenStream {
1018 let query_as = syn::parse_macro_input!(input as QueryAs);
1019 let cache = get_schemas();
1020 let (schemas, dialect) = (cache.schemas.get(), &cache.dialect);
1021 let options = TypeOptions::new()
1022 .dialect(dialect.clone())
1023 .arguments(match &dialect {
1024 SQLDialect::MariaDB => SQLArguments::QuestionMark,
1025 SQLDialect::Sqlite => SQLArguments::QuestionMark,
1026 SQLDialect::PostgreSQL | SQLDialect::PostGIS => SQLArguments::Dollar,
1027 })
1028 .list_hack(true);
1029 let mut issues = qusql_type::Issues::new(&query_as.query);
1030 let stmt = type_statement(&schemas.0, &query_as.query, &mut issues, &options);
1031
1032 let mut errors = issues_to_errors(issues.into_vec(), &query_as.query, query_as.query_span);
1033 match &stmt {
1034 qusql_type::StatementType::Select { columns, arguments } => {
1035 let (args_tokens, q) = quote_args(
1036 &mut errors,
1037 &query_as.query,
1038 query_as.last_span,
1039 &query_as.args,
1040 arguments,
1041 dialect,
1042 );
1043
1044 let row_construct = construct_row2(columns, dialect.is_postgresql());
1045 let row = query_as.as_;
1046 let s = quote! { {
1047 use ::sqlx::Arguments as _;
1048 #(#errors; )*
1049 #args_tokens
1050 sqlx::__query_with_result(#q, query_args).map(|row|
1051 #row{
1052 #(#row_construct),*
1053 }
1054 )
1055 }};
1056 s.into()
1058 }
1059 qusql_type::StatementType::Delete { .. } => {
1060 errors.push(
1061 syn::Error::new(query_as.query_span, "DELETE not support in query_as")
1062 .to_compile_error(),
1063 );
1064 quote! { {
1065 #(#errors; )*
1066 todo!("delete")
1067 }}
1068 .into()
1069 }
1070 qusql_type::StatementType::Insert {
1071 returning: None, ..
1072 } => {
1073 errors.push(
1074 syn::Error::new(
1075 query_as.query_span,
1076 "INSERT without RETURNING not support in query_as",
1077 )
1078 .to_compile_error(),
1079 );
1080 quote! { {
1081 #(#errors; )*
1082 todo!("insert")
1083 }}
1084 .into()
1085 }
1086 qusql_type::StatementType::Insert {
1087 arguments,
1088 returning: Some(returning),
1089 ..
1090 } => {
1091 let (args_tokens, q) = quote_args(
1092 &mut errors,
1093 &query_as.query,
1094 query_as.last_span,
1095 &query_as.args,
1096 arguments,
1097 dialect,
1098 );
1099
1100 let row_construct = construct_row2(returning, dialect.is_postgresql());
1101 let row = query_as.as_;
1102 let s = quote! { {
1103 use ::sqlx::Arguments as _;
1104 #(#errors; )*
1105 #args_tokens
1106 sqlx::__query_with_result(#q, query_args).map(|row|
1107 #row{
1108 #(#row_construct),*
1109 }
1110 )
1111 }};
1112 s.into()
1113 }
1114 qusql_type::StatementType::Update {
1115 returning: None, ..
1116 } => {
1117 errors.push(
1118 syn::Error::new(
1119 query_as.query_span,
1120 "UPDATE without RETURNING not support in query_as",
1121 )
1122 .to_compile_error(),
1123 );
1124 quote! { {
1125 #(#errors; )*
1126 todo!("update")
1127 }}
1128 .into()
1129 }
1130 qusql_type::StatementType::Update {
1131 arguments,
1132 returning: Some(returning),
1133 ..
1134 } => {
1135 let (args_tokens, q) = quote_args(
1136 &mut errors,
1137 &query_as.query,
1138 query_as.last_span,
1139 &query_as.args,
1140 arguments,
1141 dialect,
1142 );
1143
1144 let row_construct = construct_row2(returning, dialect.is_postgresql());
1145 let row = query_as.as_;
1146 let s = quote! { {
1147 use ::sqlx::Arguments as _;
1148 #(#errors; )*
1149 #args_tokens
1150 sqlx::__query_with_result(#q, query_args).map(|row|
1151 #row{
1152 #(#row_construct),*
1153 }
1154 )
1155 }};
1156 s.into()
1157 }
1158 qusql_type::StatementType::Replace {
1159 returning: None, ..
1160 } => {
1161 errors.push(
1162 syn::Error::new(
1163 query_as.query_span,
1164 "REPLACE without RETURNING not support in query_as",
1165 )
1166 .to_compile_error(),
1167 );
1168 quote! { {
1169 #(#errors; )*
1170 todo!("replace")
1171 }}
1172 .into()
1173 }
1174 qusql_type::StatementType::Replace {
1175 arguments,
1176 returning: Some(returning),
1177 ..
1178 } => {
1179 let (args_tokens, q) = quote_args(
1180 &mut errors,
1181 &query_as.query,
1182 query_as.last_span,
1183 &query_as.args,
1184 arguments,
1185 dialect,
1186 );
1187
1188 let row_construct = construct_row2(returning, dialect.is_postgresql());
1189 let row = query_as.as_;
1190 let s = quote! { {
1191 use ::sqlx::Arguments as _;
1192 #(#errors; )*
1193 #args_tokens
1194 sqlx::__query_with_result(#q, query_args).map(|row|
1195 #row{
1196 #(#row_construct),*
1197 }
1198 )
1199 }};
1200 s.into()
1201 }
1202 qusql_type::StatementType::Truncate => {
1203 errors.push(
1204 syn::Error::new(query_as.query_span, "TRUNCATE not supported in query_as!")
1205 .to_compile_error(),
1206 );
1207 quote! { {
1208 #(#errors; )*
1209 todo!("truncate")
1210 }}
1211 .into()
1212 }
1213 qusql_type::StatementType::Call { .. } => {
1214 errors.push(
1215 syn::Error::new(query_as.query_span, "CALL not supported in query_as!")
1216 .to_compile_error(),
1217 );
1218 quote! { {
1219 #(#errors; )*
1220 todo!("call")
1221 }}
1222 .into()
1223 }
1224 qusql_type::StatementType::Transaction => {
1225 errors.push(
1226 syn::Error::new(
1227 query_as.query_span,
1228 "Transaction control not supported in query_as!",
1229 )
1230 .to_compile_error(),
1231 );
1232 quote! { {
1233 #(#errors; )*
1234 todo!("transaction")
1235 }}
1236 .into()
1237 }
1238 qusql_type::StatementType::Set => {
1239 errors.push(
1240 syn::Error::new(query_as.query_span, "SET not supported in query_as!")
1241 .to_compile_error(),
1242 );
1243 quote! { {
1244 #(#errors; )*
1245 todo!("set")
1246 }}
1247 .into()
1248 }
1249 qusql_type::StatementType::Lock => {
1250 errors.push(
1251 syn::Error::new(query_as.query_span, "LOCK not supported in query_as!")
1252 .to_compile_error(),
1253 );
1254 quote! { {
1255 #(#errors; )*
1256 todo!("lock")
1257 }}
1258 .into()
1259 }
1260 qusql_type::StatementType::Invalid => quote! { {
1261 #(#errors; )*;
1262 todo!("invalid")
1263 }}
1264 .into(),
1265 }
1266}