Skip to content

Fix params substitution for select queries#141

Merged
long2ice merged 3 commits into
long2ice:devfrom
dmkulazhenko:fix/select-query-params
Nov 6, 2025
Merged

Fix params substitution for select queries#141
long2ice merged 3 commits into
long2ice:devfrom
dmkulazhenko:fix/select-query-params

Conversation

@dmkulazhenko

Copy link
Copy Markdown
Contributor

Select queries use substitution of arguments using % formatting, which doesn't work as far as args passed as Mapping.
This PR changes substitution formatting mechanism to str.format method.

@dmkulazhenko

Copy link
Copy Markdown
Contributor Author

oh, probably a duplicate of #138... in any case I really need this logic 😢
CC @misuzu, @stankudrow

Comment thread asynch/proto/connection.py Outdated
@stankudrow

Copy link
Copy Markdown
Contributor

@dmkulazhenko , hello.

LGTM, tests passed, mypy is OK with these files.

mypy_report

@misuzu

@dmkulazhenko

Copy link
Copy Markdown
Contributor Author

@stankudrow, changelog entry and test for KeyError trigger added

@ayratsa

ayratsa commented Sep 3, 2025

Copy link
Copy Markdown

@misuzu, please, proceed with the PR

@stankudrow

stankudrow commented Sep 21, 2025

Copy link
Copy Markdown
Contributor

@dmkulazhenko , hello.

Any updates about the suggestions above?

@stankudrow

Copy link
Copy Markdown
Contributor

@dmkulazhenko , hello.

Are you willing to finish this PR including the suggestions above?

@dmkulazhenko

dmkulazhenko commented Oct 29, 2025

Copy link
Copy Markdown
Contributor Author

@dmkulazhenko , hello.

Are you willing to finish this PR including the suggestions above?

@stankudrow, maybe I get it wrong, but what should I do?

  • changelog update
  • test added
  • question regarding try...except answered

upd: seems to be you expect that I will drop try...except, I don't mind so much about it, so will do it in a couple of minutes

@dmkulazhenko

Copy link
Copy Markdown
Contributor Author

@stankudrow, re-review pls

@stankudrow

Copy link
Copy Markdown
Contributor

@stankudrow, re-review pls

@dmkulazhenko , done.

@DaniilAnichin , could you also have a look, please? Looks good to me, that last time I ran mypy over these files the checker wasn't unpleased.

@DaniilAnichin DaniilAnichin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, no comments

@baconfield

Copy link
Copy Markdown
Contributor

This release might want a warning/heads up for those upgrading, as this breaks existing code that already uses the %(name)s formatting in queries

@stankudrow

stankudrow commented Nov 12, 2025

Copy link
Copy Markdown
Contributor

This release might want a warning/heads up for those upgrading, as this breaks existing code that already uses the %(name)s formatting in queries

Hello.

Could you possibly list here a small example that demonstrates your point?

Also, please do feel free to open a PR (if you like) that 1) does not decline the changes (to a certain extent) from this PR and 2) allows to handle the case you have mentioned before so the compatibility wouldn't break. It is possible to mitigate risks while this work isn't really released.

@baconfield

baconfield commented Nov 12, 2025

Copy link
Copy Markdown
Contributor

Could you possibly list here a small example that demonstrates your point?

The simplest example I have would be a query like this:

await self.cursor.execute(
    """
    SELECT
        EXISTS(
            SELECT 1
            FROM table_a
            WHERE profile_id = %(profile_id)s
        ) AS a_enabled,
        EXISTS(
            SELECT 1
            FROM table_b
            WHERE profile_id = %(profile_id)s
        ) AS b_enabled
    """,
    {"profile_id": profile_id}
)

Also, please do feel free to open a PR (if you like) that 1) does not decline the changes (to a certain extent) from this PR and 2) allows to handle the case you have mentioned before so the compatibility wouldn't break. It is possible to mitigate risks while this work isn't really released.

Another parameter for setting this came to mind, but imagine that would be pretty clunky. The other alternative was this, but unsure of this possibly having gotchas down the line.

    @staticmethod
    def substitute_params(query: str, params: Mapping[str, Any]) -> str:
        if not isinstance(params, Mapping):
            raise ValueError("Parameters are expected to be a mapping")

        first_param = next(iter(params))
        if f"{{{first_param}}}" in query:
            return query.format(**escape_params(params))
        else:
            return query % escape_params(params)

@stankudrow

Copy link
Copy Markdown
Contributor

Could you possibly list here a small example that demonstrates your point?

The simplest example I have would be a query like this:

await self.cursor.execute(
    """
    SELECT
        EXISTS(
            SELECT 1
            FROM table_a
            WHERE profile_id = %(profile_id)s
        ) AS a_enabled,
        EXISTS(
            SELECT 1
            FROM table_b
            WHERE profile_id = %(profile_id)s
        ) AS b_enabled
    """,
    {"profile_id": profile_id}
)

Also, please do feel free to open a PR (if you like) that 1) does not decline the changes (to a certain extent) from this PR and 2) allows to handle the case you have mentioned before so the compatibility wouldn't break. It is possible to mitigate risks while this work isn't really released.

Another parameter for setting this came to mind, but imagine that would be pretty clunky. The other alternative was this, but unsure of this possibly having gotchas down the line.

    @staticmethod
    def substitute_params(query: str, params: Mapping[str, Any]) -> str:
        if not isinstance(params, Mapping):
            raise ValueError("Parameters are expected to be a mapping")

        first_param = next(iter(params))
        if f"{{{first_param}}}" in query:
            return query.format(**escape_params(params))
        else:
            return query % escape_params(params)

Hm, what to try, e.g., this solution?

And the snippet may be like this:
fix_escape_params_proposal

@baconfield , would you like to open a PR and try this or any better solution or should I do this?

Also, consider updating tests on:

  • C-style strings;
  • formatable strings;
  • test no, one and multiple (two will be enough) placeholders for both styles.

@baconfield

baconfield commented Nov 12, 2025

Copy link
Copy Markdown
Contributor

would you like to open a PR and try this or any better solution or should I do this?

Yeah that looks like a better approach, I don't mind making a PR, it will just be a bit as I'll need to wait until after hours tonight.

@dmkulazhenko

Copy link
Copy Markdown
Contributor Author

@stankudrow @baconfield , tbh it's totally my fault... I feel really stupid right now, because I had no idea that old python format style have an ability to pass arguments ("%(arg)s" % {"arg": "val"} == "val").

So, the whole idea of this PR is pointless... My initial problem was just lack of knowledge of old-style python formatting 😢. I believe @barakor had same intentions in #138.

There is 3 options to do now:

  1. revert this PR (looks like the easiest and most valid option) and do a release;
  2. make some generic approach that will determine parameters style on-the-fly (looks cumbersome and kinda pointless);
  3. force users to write queries in new style (bad behavior in global + adds a lot of headache to libs where asynch used as upstream e.g. https://github.com/nebuly-ai/clickhouse-sqlalchemy).

If with 1st and 2nd options implementation is clear, 3rd option will require changes not only in clickhouse-sqlalchemy, but also in sqlalchemy:

As you can see there is no option like "{name}" 😞.


To mitigate such PRs in future it's a good idea to add section with an example of parametrized query or a link to https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting. Btw, finding this documentations by googling something like "python named percent formatting" is not that easy, thanks to dummy guides copy-pasters 🙃.

baconfield added a commit to baconfield/asynch that referenced this pull request Dec 6, 2025
Leechael pushed a commit to Leechael/asynch that referenced this pull request May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants