Skip to content

Add named parameter support#795

Merged
mfussenegger merged 4 commits intocrate:mainfrom
bgunebakan:feature/named-parameter-support
Mar 31, 2026
Merged

Add named parameter support#795
mfussenegger merged 4 commits intocrate:mainfrom
bgunebakan:feature/named-parameter-support

Conversation

@bgunebakan
Copy link
Copy Markdown
Contributor

@bgunebakan bgunebakan commented Mar 23, 2026

Summary of the changes / Why this is an improvement

Adds client-side named parameter support (pyformat paramstyle) to the CrateDB Python client, addressing the request in #774.

Previously, cursor.execute() only accepted positional ? placeholders. Users with complex SQL had to maintain a positional list that was error-prone and hard to read.

With this change, a dict can be passed as the parameters argument using %(name)s placeholders:

cursor.execute(
    "SELECT * FROM t WHERE city = %(city)s AND kiez = %(kiez)s",
    {"city": "Berlin", "kiez": "Kreuzberg"},
)

The same parameter name may appear multiple times in the query, each occurrence is resolved independently:

cursor.execute(
    "SELECT * FROM t WHERE state = %(q)s OR city = %(q)s",
    {"q": "Berlin"},
)

Positional ? queries continue to work unchanged, no breaking change.

Known limitation

Supporting named params in bulk operations are not supported because it requires a different approach from execute(). We need to extract the ordered parameter name list from the SQL. Excluded from this PR to keep this PR focused on the core execute() case.

Checklist

@amotl amotl requested review from mfussenegger and seut March 24, 2026 23:10
@mfussenegger
Copy link
Copy Markdown
Member

mfussenegger commented Mar 25, 2026

Thank you for the contribution.
Could you also add an entry to the CHANGES.rst? Otherwise lgtm

_NAMED_PARAM_RE = re.compile(r"%\((\w+)\)s")


def convert_named_to_positional(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd tend to move the method into the cursor module and prefix it with _.

It's not supposed to be public API and a dedicated module doesn't seem warranted for this tiny and simple function.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call. I was thinking to isolate the regex logic in its own module to keep cursor.py focused on DB-API concerns and make the function independently testable.

I'm moving to _convert_named_to_positional and _NAMED_PARAM_RE directly into cursor.py at module level (not inside the class, since it doesn't touch self or any class state). And will delete params.py. What do you think about this structure?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm moving to _convert_named_to_positional and _NAMED_PARAM_RE directly into cursor.py at module level (not inside the class, since it doesn't touch self or any class state). And will delete params.py. What do you think about this structure?

Sounds good

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for your review. I refactored the code based on that.

@mfussenegger mfussenegger changed the title Feature/named parameter support Add named parameter support Mar 25, 2026
…module

Docs: Update CHANGES.rst based on new implementation
@mfussenegger mfussenegger enabled auto-merge (squash) March 31, 2026 05:45
@mfussenegger mfussenegger merged commit 93b3ba6 into crate:main Mar 31, 2026
15 checks passed
Comment on lines +516 to +519
cursor.execute("SELECT %(x)s, %(x)s", {"x": 42})
mocked_connection.client.sql.assert_called_once_with(
"SELECT ?, ?", [42, 42], None
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Btw. as a follow up we should change this behavior to have this return "SELECT $1, $1", [42]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for suggestion. We can keep track of of already seen names and reuse them inside _convert_named_to_positional() How should we follow up? should I open a new issue or create a PR directly?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I already created a PR: #796

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok super!

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.

Parameter binding failure with string containing "{{value}}" and lack of named parameter support

4 participants