Is there a way to evaluate a declarative macro early?

Hi, I'm trying to write a wrapper macro for sqlx to give me some more compile-time guarantees for a use-case that is not supported by mariadb (binding a list to ?). For that I want to first concat! something to the query, before passing the result to sqlx' query_as! macro:

macro_rules! query_in_list {
    ($ty:ty, $query:literal for $name:ident in $list:ident) => {
        query_in_list!($ty, $query for $name in $list { $name })
    };
    ($ty:ty, $query:literal for $name:ident in $list:ident { $block:expr }) => {{
        let _ = sqlx::query_as!($ty, concat!($query, "( ? );"));
        let _ = sqlx::query_as!($ty, concat!($query, "( ?, ? );"));

        let mut builder = sqlx::QueryBuilder::new(concat!($query, " ( "));
        let mut separated = builder.separated(", ");
        for $name in $list {
            separated.push_bind($block);
        }
        separated.push_unseparated(" );");

        builder
    }};
}

let builder = query_in_list! {
    OrgPermissionsQueryResult,
    r#"SELECT organization_id AS org_id, permissions
       FROM roles
       JOIN roles_permissions_organization USING (role_id)
       WHERE idp_group IN "#
    for group in groups
};

This however does not work, as it tries to pass the whole concat!(...) into the macro, instead of just the result? Is there any way to evaluate it early? e.g. concat!! or passing it to an "early evaluate" macro or something similar?

macros just "shuffles" tokens around, you can't "evalute" a macro.

yes, it passes the concat!(...) as a whole into query_as!(). however, depending on how query_as!() is defined, it may or may not work.

I have not used sqlx personally, but the documentation of query_as!() indicates it expects an expr, so I assume it should work in this case.

I'm not sure why it doesn't work in this case, but you could separate the problem in two parts: first expand it iteratively to see what the concat! produces, or make a dummy, simpler declarative macro with the same pattern but only that concat! alone and print out the result. Then in a second step, give that output to query_as! and see if that's where the problem occurs.

Why would you like to evaluate the concat! before passing it to another declarative macro? Here, I suppose you could assign a temporary variable, but I don't see the advantage.

query_as! expects a string literal as an input, so I would need the concat! macro to create the literal first and then pass that into query_as

the query_as macro only supports string literals, so I literally cannot pass in the macro, but the string literal it produces.

Ah, I see. Have you tried with $query:expr instead? Unless there's a specific reason to use the literal specifier.

There absolutely is a reason to pass literal: the whole point of sqlx is to parse that literal to validate it. Kinda like format_args. And, of course, format_args does what @will_i_code wants… and that's why it's not a regular macro but part of the compiler.

It's one of the very well-known things that was ripped out when they rushed to stability Rust 1.0 about 10 years ago. Lindy's law tells us ETA for that feature return is 10 years now…

1 Like

sqlx::query_as! actually uses the expr specifier for that parameter.

But it seems to use a procedural macro to expand it... I'm not sure why they made that choice instead of literal, but then again, it "absolutely" is only version 0.8.6.