tank_core/
util.rs

1use proc_macro2::TokenStream;
2use quote::{ToTokens, TokenStreamExt, quote};
3use std::{
4    borrow::Cow,
5    cmp::min,
6    collections::BTreeMap,
7    ffi::{CStr, CString},
8    fmt::Write,
9    ptr,
10};
11use syn::Path;
12
13#[derive(Clone)]
14/// Polymorphic iterator adapter returning items from either variant.
15pub enum EitherIterator<A, B>
16where
17    A: Iterator,
18    B: Iterator<Item = A::Item>,
19{
20    Left(A),
21    Right(B),
22}
23impl<A, B> Iterator for EitherIterator<A, B>
24where
25    A: Iterator,
26    B: Iterator<Item = A::Item>,
27{
28    type Item = A::Item;
29    fn next(&mut self) -> Option<Self::Item> {
30        match self {
31            EitherIterator::Left(a) => a.next(),
32            EitherIterator::Right(b) => b.next(),
33        }
34    }
35}
36
37/// Quote a `BTreeMap<K, V>` into tokens.
38pub fn quote_btree_map<K: ToTokens, V: ToTokens>(value: &BTreeMap<K, V>) -> TokenStream {
39    let mut tokens = TokenStream::new();
40    for (k, v) in value {
41        let ks = k.to_token_stream();
42        let vs = v.to_token_stream();
43        tokens.append_all(quote! {
44            (#ks, #vs),
45        });
46    }
47    quote! {
48        ::std::collections::BTreeMap::from([
49            #tokens
50        ])
51    }
52}
53
54/// Quote a `Cow<T>` preserving borrowed vs owned status for generated code.
55pub fn quote_cow<T: ToOwned + ToTokens + ?Sized>(value: &Cow<T>) -> TokenStream
56where
57    <T as ToOwned>::Owned: ToTokens,
58{
59    match value {
60        Cow::Borrowed(v) => quote! { ::std::borrow::Cow::Borrowed(#v) },
61        Cow::Owned(v) => quote! { ::std::borrow::Cow::Borrowed(#v) },
62    }
63}
64
65/// Quote an `Option<T>` into tokens.
66pub fn quote_option<T: ToTokens>(value: &Option<T>) -> TokenStream {
67    match value {
68        None => quote! { None },
69        Some(v) => quote! { Some(#v) },
70    }
71}
72
73/// Determine if the trailing segments of a `syn::Path` match the expected identifiers.
74pub fn matches_path(path: &Path, expect: &[&str]) -> bool {
75    let len = min(path.segments.len(), expect.len());
76    path.segments
77        .iter()
78        .rev()
79        .take(len)
80        .map(|v| &v.ident)
81        .eq(expect.iter().rev().take(len))
82}
83
84/// Write an iterator of items separated by a delimiter into a string.
85pub fn separated_by<T, F>(
86    out: &mut String,
87    values: impl IntoIterator<Item = T>,
88    mut f: F,
89    separator: &str,
90) where
91    F: FnMut(&mut String, T),
92{
93    let mut len = out.len();
94    for v in values {
95        if out.len() > len {
96            out.push_str(separator);
97        }
98        len = out.len();
99        f(out, v);
100    }
101}
102
103/// Convenience wrapper converting into a `CString`.
104pub fn as_c_string(str: impl Into<Vec<u8>>) -> CString {
105    CString::new(
106        str.into()
107            .into_iter()
108            .map(|b| if b == 0 { b'?' } else { b })
109            .collect::<Vec<u8>>(),
110    )
111    .unwrap_or_default()
112}
113
114pub fn error_message_from_ptr<'a>(ptr: &'a *const i8) -> Cow<'a, str> {
115    unsafe {
116        if *ptr != ptr::null() {
117            CStr::from_ptr(*ptr).to_string_lossy()
118        } else {
119            Cow::Borrowed("Unknown error: could not extract the error message")
120        }
121    }
122}
123
124/// Consume a prefix of `input` while the predicate returns true, returning that slice.
125pub fn consume_while<'s>(input: &mut &'s str, predicate: impl FnMut(&char) -> bool) -> &'s str {
126    let len = input.chars().take_while(predicate).count();
127    if len == 0 {
128        return "";
129    }
130    let result = &input[..len];
131    *input = &input[len..];
132    result
133}
134
135pub fn extract_number<'s, const SIGNED: bool>(input: &mut &'s str) -> &'s str {
136    let mut end = 0;
137    let mut chars = input.chars().peekable();
138    if SIGNED && matches!(chars.peek(), Some('+') | Some('-')) {
139        chars.next();
140        end += 1;
141    }
142    for _ in chars.take_while(char::is_ascii_digit) {
143        end += 1;
144    }
145    let result = &input[..end];
146    *input = &input[end..];
147    result
148}
149
150pub fn print_timer(out: &mut String, quote: &str, h: i64, m: u8, s: u8, ns: u32) {
151    let mut subsecond = ns;
152    let mut width = 9;
153    while width > 1 && subsecond % 10 == 0 {
154        subsecond /= 10;
155        width -= 1;
156    }
157    let _ = write!(
158        out,
159        "{quote}{h:02}:{m:02}:{s:02}.{subsecond:0width$}{quote}",
160    );
161}
162
163#[macro_export]
164macro_rules! number_to_month {
165    ($month:expr, $throw:expr $(,)?) => {
166        match $month {
167            1 => Month::January,
168            2 => Month::February,
169            3 => Month::March,
170            4 => Month::April,
171            5 => Month::May,
172            6 => Month::June,
173            7 => Month::July,
174            8 => Month::August,
175            9 => Month::September,
176            10 => Month::October,
177            11 => Month::November,
178            12 => Month::December,
179            _ => $throw,
180        }
181    };
182}
183
184#[macro_export]
185macro_rules! month_to_number {
186    ($month:expr $(,)?) => {
187        match $month {
188            Month::January => 1,
189            Month::February => 2,
190            Month::March => 3,
191            Month::April => 4,
192            Month::May => 5,
193            Month::June => 6,
194            Month::July => 7,
195            Month::August => 8,
196            Month::September => 9,
197            Month::October => 10,
198            Month::November => 11,
199            Month::December => 12,
200        }
201    };
202}
203
204#[macro_export]
205/// Conditionally wrap a generated fragment in parentheses.
206macro_rules! possibly_parenthesized {
207    ($out:ident, $cond:expr, $v:expr) => {
208        if $cond {
209            $out.push('(');
210            $v;
211            $out.push(')');
212        } else {
213            $v;
214        }
215    };
216}
217
218#[macro_export]
219/// Truncate long strings for logging and error messages purpose.
220///
221/// Returns a `format_args!` that yields at most 497 characters from the start
222/// of the input followed by `...` when truncation occurred. Minimal overhead.
223///
224/// # Examples
225/// ```ignore
226/// use tank_core::truncate_long;
227/// let short = "SELECT 1";
228/// assert_eq!(format!("{}", truncate_long!(short)), "SELECT 1\n");
229/// let long = format!("SELECT {}", "X".repeat(600));
230/// let logged = format!("{}", truncate_long!(long));
231/// assert!(logged.starts_with("SELECT XXXXXX"));
232/// assert!(logged.ends_with("...\n"));
233/// ```
234macro_rules! truncate_long {
235    ($query:expr) => {
236        format_args!(
237            "{}{}",
238            &$query[..::std::cmp::min($query.len(), 497)].trim(),
239            if $query.len() > 497 { "...\n" } else { "" },
240        )
241    };
242}
243
244/// Sends the value through the channel and logs in case of error.
245///
246/// Parameters:
247/// * `$tx`: sender channel
248/// * `$value`: value to be sent
249///
250/// *Example*:
251/// ```ignore
252/// send_value!(tx, Ok(QueryResult::Row(row)));
253/// ```
254
255#[macro_export]
256macro_rules! send_value {
257    ($tx:ident, $value:expr) => {{
258        if let Err(e) = $tx.send($value) {
259            log::error!("{e:#}");
260        }
261    }};
262}
263
264/// Incrementally accumulates tokens from a speculative parse stream until one
265/// of the supplied parsers succeeds.
266///
267/// Returns `(accumulated_tokens, (parser1_option, parser2_option, ...))` with
268/// exactly one `Some(T)`: the first successful parser.
269#[doc(hidden)]
270#[macro_export]
271macro_rules! take_until {
272    ($original:expr, $($parser:expr),+ $(,)?) => {{
273        let macro_local_input = $original.fork();
274        let mut macro_local_result = (
275            TokenStream::new(),
276            ($({
277                let _ = $parser;
278                None
279            }),+),
280        );
281        loop {
282            if macro_local_input.is_empty() {
283                break;
284            }
285            let mut parsed = false;
286            let produced = ($({
287                let attempt = macro_local_input.fork();
288                if let Ok(content) = ($parser)(&attempt) {
289                    macro_local_input.advance_to(&attempt);
290                    parsed = true;
291                    Some(content)
292                } else {
293                    None
294                }
295            }),+);
296            if parsed {
297                macro_local_result.1 = produced;
298                break;
299            }
300            macro_local_result.0.append(macro_local_input.parse::<TokenTree>()?);
301        }
302        $original.advance_to(&macro_local_input);
303        macro_local_result
304    }};
305}
306
307#[macro_export]
308/// Implement the `Executor` trait for a transaction wrapper type by
309/// delegating each operation to an underlying connection object.
310///
311/// This reduces boilerplate across driver implementations. The macro expands
312/// into an `impl Executor for $transaction<'c>` with forwarding methods for
313/// `prepare`, `run`, `fetch`, `execute`, and `append`.
314///
315/// Parameters:
316/// * `$driver`: concrete driver type.
317/// * `$transaction`: transaction wrapper type (generic over lifetime `'c`).
318/// * `$connection`: field name on the transaction pointing to the connection.
319///
320/// # Examples
321/// ```ignore
322/// use crate::{YourDBConnection, YourDBDriver};
323/// use tank_core::{Error, Result, Transaction, impl_executor_transaction};
324///
325/// pub struct YourDBTransaction<'c> {
326///     connection: &'c mut YourDBConnection,
327/// }
328///
329/// impl_executor_transaction!(YourDBDriver, YourDBTransaction<'c>, connection);
330///
331/// impl<'c> Transaction<'c> for YourDBTransaction<'c> { ... }
332/// ```
333macro_rules! impl_executor_transaction {
334    // Case 1: Lifetime is present (necessary for transactions)
335    ($driver:ty, $transaction:ident $(< $lt:lifetime >)?, $connection:ident) => {
336       impl $(<$lt>)? ::tank_core::Executor for $transaction $(<$lt>)? {
337            type Driver = $driver;
338
339            fn accepts_multiple_statements(&self) -> bool {
340                self.$connection.accepts_multiple_statements()
341            }
342
343            fn prepare(
344                &mut self,
345                query: String,
346            ) -> impl Future<Output = ::tank_core::Result<::tank_core::Query<Self::Driver>>> + Send
347            {
348                self.$connection.prepare(query)
349            }
350
351            fn run<'s>(
352                &'s mut self,
353                query: impl ::tank_core::AsQuery<Self::Driver> + 's,
354            ) -> impl ::tank_core::stream::Stream<
355                Item = ::tank_core::Result<::tank_core::QueryResult>,
356            > + Send {
357                self.$connection.run(query)
358            }
359
360            fn fetch<'s>(
361                &'s mut self,
362                query: impl ::tank_core::AsQuery<Self::Driver> + 's,
363            ) -> impl ::tank_core::stream::Stream<
364                Item = ::tank_core::Result<::tank_core::RowLabeled>,
365            > + Send
366            + 's {
367                self.$connection.fetch(query)
368            }
369
370            fn execute<'s>(
371                &'s mut self,
372                query: impl ::tank_core::AsQuery<Self::Driver> + 's,
373            ) -> impl Future<Output = ::tank_core::Result<::tank_core::RowsAffected>> + Send {
374                self.$connection.execute(query)
375            }
376
377            fn append<'a, E, It>(
378                &mut self,
379                entities: It,
380            ) -> impl Future<Output = ::tank_core::Result<::tank_core::RowsAffected>> + Send
381            where
382                E: ::tank_core::Entity + 'a,
383                It: IntoIterator<Item = &'a E> + Send,
384                <It as IntoIterator>::IntoIter: Send,
385            {
386                self.$connection.append(entities)
387            }
388        }
389    }
390}