mik_sql_macros/
lib.rs

1// =============================================================================
2// CRATE-LEVEL QUALITY LINTS (following Tokio/Serde standards)
3// =============================================================================
4#![forbid(unsafe_code)]
5#![deny(unused_must_use)]
6#![warn(missing_docs)]
7#![warn(missing_debug_implementations)]
8#![warn(rust_2018_idioms)]
9// Note: unreachable_pub is not applicable to proc-macro crates where internal
10// functions need pub visibility for module organization but aren't exported
11#![warn(rustdoc::missing_crate_level_docs)]
12#![warn(rustdoc::broken_intra_doc_links)]
13// =============================================================================
14// CLIPPY CONFIGURATION FOR PROC-MACRO CRATES
15// =============================================================================
16// These lints are relaxed for proc-macro crates where syn/quote patterns are used
17#![allow(clippy::indexing_slicing)] // Checked by syn parsing structure
18#![allow(elided_lifetimes_in_paths)] // Common pattern with ParseStream
19
20//! Proc-macros for mik-sql - SQL query builder with Mongo-style filters.
21
22use proc_macro::TokenStream;
23use quote::quote;
24use syn::{
25    Expr, Result, Token,
26    parse::{Parse, ParseStream},
27    parse_macro_input,
28};
29
30mod common;
31mod create;
32mod delete;
33mod read;
34mod update;
35
36// ============================================================================
37// SQL CRUD MACROS - Query builder with JSON-like syntax
38// ============================================================================
39
40/// Build a SELECT query using the query builder (CRUD: Read).
41///
42/// # Example
43/// ```ignore
44/// let (sql, params) = sql_read!(users {
45///     select: [id, name, email],
46///     filter: { active: true },
47///     order: name,
48///     limit: 10,
49/// });
50/// ```
51#[proc_macro]
52pub fn sql_read(input: TokenStream) -> TokenStream {
53    read::sql_read_impl(input)
54}
55
56/// Build an INSERT query using object-like syntax (CRUD: Create).
57///
58/// # Example
59/// ```ignore
60/// let (sql, params) = sql_create!(users {
61///     name: str(name),
62///     email: str(email),
63///     returning: [id],
64/// });
65/// ```
66#[proc_macro]
67pub fn sql_create(input: TokenStream) -> TokenStream {
68    create::sql_create_impl(input)
69}
70
71/// Build an UPDATE query using object-like syntax (CRUD: Update).
72///
73/// # Example
74/// ```ignore
75/// let (sql, params) = sql_update!(users {
76///     set: { name: str(new_name) },
77///     filter: { id: int(user_id) },
78/// });
79/// ```
80#[proc_macro]
81pub fn sql_update(input: TokenStream) -> TokenStream {
82    update::sql_update_impl(input)
83}
84
85/// Build a DELETE query using object-like syntax (CRUD: Delete).
86///
87/// # Example
88/// ```ignore
89/// let (sql, params) = sql_delete!(users {
90///     filter: { id: int(user_id) },
91/// });
92/// ```
93#[proc_macro]
94pub fn sql_delete(input: TokenStream) -> TokenStream {
95    delete::sql_delete_impl(input)
96}
97
98// ============================================================================
99// IDS MACRO - Collect field values from a list for batched loading
100// ============================================================================
101
102/// Collect field values from a list for batched loading.
103///
104/// # Example
105/// ```ignore
106/// let user_ids = ids!(users);           // extracts .id from each item
107/// let author_ids = ids!(posts, author_id);  // extracts .author_id from each item
108/// ```
109#[proc_macro]
110pub fn ids(input: TokenStream) -> TokenStream {
111    let input = parse_macro_input!(input as IdsInput);
112
113    let list = &input.list;
114    let field = &input.field;
115
116    let tokens = quote! {
117        #list.iter().map(|__item| __item.#field.clone()).collect::<Vec<_>>()
118    };
119
120    TokenStream::from(tokens)
121}
122
123struct IdsInput {
124    list: Expr,
125    field: syn::Ident,
126}
127
128impl Parse for IdsInput {
129    fn parse(input: ParseStream) -> Result<Self> {
130        let list: Expr = input.parse()?;
131
132        let field = if input.peek(Token![,]) {
133            input.parse::<Token![,]>()?;
134            input.parse()?
135        } else {
136            syn::Ident::new("id", proc_macro2::Span::call_site())
137        };
138
139        Ok(Self { list, field })
140    }
141}