Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⭐ Improve parameterized query support - fixes #793 #794

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

mvanderlee
Copy link

@mvanderlee mvanderlee commented Feb 22, 2024

Fixes #793
The implemented tests speak for themselves, but here is an extract:

subquery = (
    Query.from_(self.table_efg)
    .select(self.table_efg.fiz, self.table_efg.buz)
    .where(self.table_efg.buz == 'buz')
)

q = (
    Query.from_(self.table_abc)
    .join(subquery)
    .on(self.table_abc.bar == subquery.buz)
    .select(self.table_abc.foo, subquery.fiz)
    .where(self.table_abc.bar == 'bar')
)

parameter = NamedParameter()
sql = q.get_sql(parameter=parameter)
self.assertEqual(
    'SELECT "abc"."foo","sq0"."fiz" FROM "abc" JOIN (SELECT "fiz","buz" FROM "efg" WHERE "buz"=:param1)'
    ' "sq0" ON "abc"."bar"="sq0"."buz" WHERE "abc"."bar"=:param2',
    sql,
)
self.assertEqual({'param1': 'buz', 'param2': 'bar'}, parameter.get_parameters())

This would allow the user to easily plug into SQLAlchemy for example:

parameter = NamedParameter()
session.execute(text(q.get_sql(parameter=parameter)), **parameter.get_parameters())

@mvanderlee mvanderlee requested a review from a team as a code owner February 22, 2024 00:13
@mvanderlee mvanderlee mentioned this pull request Feb 22, 2024
pypika/terms.py Outdated
@property
def placeholder(self):
if callable(self._placeholder):
return self._placeholder(None)
Copy link
Contributor

Choose a reason for hiding this comment

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

how does this work on None?

Copy link
Author

Choose a reason for hiding this comment

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

callable Returns False
image

But I presume you mean how will the self_placeholder(None) work with None.
Good point, it wouldn't atm with the default generators.

I will fix it now.

@wd60622
Copy link
Contributor

wd60622 commented Feb 22, 2024

Thanks for the PR! Will take a deeper look later

@coveralls
Copy link

Coverage Status

coverage: 98.331% (-0.06%) from 98.39%
when pulling 66c6310 on mvanderlee:mvanderlee/parametized_query
into 8841520 on kayak:master.

@mvanderlee
Copy link
Author

Thanks for the PR! Will take a deeper look later

@wd60622 Do you think you'll have time this week?

Copy link
Contributor

@wd60622 wd60622 left a comment

Choose a reason for hiding this comment

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

Looks really good.
Just some comments on type hints, raising errors, and combining logic

pypika/terms.py Outdated Show resolved Hide resolved
pypika/terms.py Outdated Show resolved Hide resolved
def named_placeholder_gen(idx: int) -> str:
return f'param{idx + 1}'


class Parameter(Term):
is_aggregate = None

def __init__(self, placeholder: Union[str, int]) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

Type hint here doesn't include callable? Is that correct still?

Copy link
Author

Choose a reason for hiding this comment

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

Correct, only ListParameter and DictParameter (and classes that inherit from them) support callable.
The base parameter does not.

class Parameter(Term):
is_aggregate = None

def __init__(self, placeholder: Union[str, int]) -> None:
super().__init__()
self.placeholder = placeholder
self._placeholder = placeholder
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be good to use a setter here. With that, implementing a check for the correct type can happen on initialization if put in the setter

Copy link
Author

Choose a reason for hiding this comment

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

I'm not sure I follow how to do this pythonically? Could you share some pseudo code to show what you mean?

Copy link
Contributor

Choose a reason for hiding this comment

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

@mvanderlee I believe @wd60622 was referring to the following:

@property
def placeholder(self):
      return self._placeholder

@placeholder.setter
def placeholder(self, placeholder):
     # can add checks here if needed
     self._placeholder = placeholder

Copy link
Author

Choose a reason for hiding this comment

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

Right, which adds no value and isn't pythonic. This isn't Java/C#.

It would also break parameters. After a parameter has been created and used, any changes to the placeholder will break queries. Which is why I've made it private with a getter only.

I haven't changed the type, so any type checks would have to be part of a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would disagree with you on it not being Pythonic, as it’s a native language feature.

Also, I was trying to be helpful, but given your receptiveness to discussion and tone, I won’t proceed.

Good luck.

Copy link
Author

Choose a reason for hiding this comment

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

Sorry for my tone @danielenricocahall it'd been a rough week and I lashed out.

As for pythonic. This video does a good job at explaining language feature vs pythonic.
Raymond Hettinger - Beyond PEP 8 -- Best practices for beautiful intelligible code - https://www.youtube.com/watch?v=wf-BqAjZb8M

@wd60622
Copy link
Contributor

wd60622 commented Mar 13, 2024

Do you have any thoughts as well? @AzisK

@mvanderlee
Copy link
Author

@wd60622 @AzisK any update on this?
It should go hand in hand with #792

@AzisK
Copy link

AzisK commented Apr 26, 2024

Could we also have this functionality described in pypika docs?

@AzisK
Copy link

AzisK commented Apr 26, 2024

The tests look really nice. What about adding PyformatParameter in the tests as well?

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.

Improved parameter support
5 participants