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

Add parametrized queries #137

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ac8f4d1
Add: param argument
tonykploomber Feb 17, 2023
f52af0a
Add: param arguments
tonykploomber Feb 20, 2023
f24edf2
Format
tonykploomber Feb 20, 2023
34081f4
Add: legacy support
tonykploomber Feb 22, 2023
d92c313
Revert: magic
tonykploomber Feb 22, 2023
662ce22
Comment
tonykploomber Feb 22, 2023
3e15aee
Move: var_expand func
tonykploomber Feb 22, 2023
d40d5ad
Revert "Move: var_expand func"
tonykploomber Feb 22, 2023
b922ff8
Add: more test cases
tonykploomber Feb 22, 2023
01f056c
Update: regex
tonykploomber Feb 22, 2023
a9dd770
Add: more test cases
tonykploomber Feb 22, 2023
da49c59
Add: more test cases
tonykploomber Feb 22, 2023
808f0d0
Add: CHANGELOG
tonykploomber Feb 23, 2023
143b1af
Remove: global & parameter support code
tonykploomber Feb 28, 2023
269447d
Add: Ref to original issue in comment
tonykploomber Feb 28, 2023
ff8c1d4
Add: PendingDeprecationWarning & Test cases
tonykploomber Feb 28, 2023
ee458a7
Add: doc
tonykploomber Feb 28, 2023
dbf7dc4
Update: intro
tonykploomber Feb 28, 2023
a010cc3
Add: doc
tonykploomber Feb 28, 2023
5e36836
Merge branch 'master' of github.com:tonykploomber/jupysql into 93-add…
tonykploomber Feb 28, 2023
4a23c10
Merge
tonykploomber Feb 28, 2023
7cb4c28
Add: vs.html
tonykploomber Feb 28, 2023
ca52b8d
Fix: merge issue
tonykploomber Feb 28, 2023
0902aef
Make vs.html build success
tonykploomber Feb 28, 2023
24505da
Add: template link in vs.html, this will expected failed
tonykploomber Feb 28, 2023
2381263
Rebuild
tonykploomber Feb 28, 2023
6c4d9cb
Fix: variable typo?
tonykploomber Feb 28, 2023
dff8b23
Add: see more template link
tonykploomber Feb 28, 2023
365dd14
Revert "Add: see more template link"
tonykploomber Mar 1, 2023
54c623f
Merge remote-tracking branch 'upstream/master' into 93-adding-paramet…
tonykploomber Mar 1, 2023
a8568e6
Merge remote-tracking branch 'upstream/master' into 93-adding-paramet…
tonykploomber Mar 2, 2023
00a45eb
fix: comments
tonykploomber Mar 2, 2023
4a5cf3f
Fix: test cases
tonykploomber Mar 2, 2023
e74be16
Depth
tonykploomber Mar 2, 2023
11beb24
Depth
tonykploomber Mar 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* [API Change] Drops support for old versions of IPython (removed imports from `IPython.utils.traitlets`)
* [Feature] Adds `%%config SqlMagic.autopolars = True` ([#138](https://github.com/ploomber/jupysql/issues/138))

* [Feature] Support new variable substitution as {{a}} format ([#137](https://github.com/ploomber/jupysql/pull/137))

## 0.5.6 (2023-02-16)

* [Feature] Shows missing driver package suggestion message ([#124](https://github.com/ploomber/jupysql/issues/124))
Expand Down
1 change: 1 addition & 0 deletions doc/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ parts:
- file: compose
- file: user-guide/tables-columns
- file: plot-legacy
- file: user-guide/template

- caption: Integrations
chapters:
Expand Down
3 changes: 2 additions & 1 deletion doc/community/vs.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ JupySQL is an actively maintained fork of [ipython-sql](https://github.com/cathe

If you're migrating from `ipython-sql` to JupySQL, these are the differences (it most cases, no code changes are needed):

- Since `0.6` JupySQL no longer supports old versions of IPython
- Since `0.6` JupySQL no longer supports old versions of IPython
- Variable expansion is being replaced from `{variable}`, `${variable}` to `{{variable}}`
6 changes: 6 additions & 0 deletions doc/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ or a single dictionary with a tuple of scalar values per key (``result.dict()``)

## Variable substitution

```{versionchanged} 0.5.7
tonykploomber marked this conversation as resolved.
Show resolved Hide resolved
This is a legacy API that's kept for backwards compatibility.
```

Bind variables (bind parameters) can be used in the "named" (:x) style.
The variable names used should be defined in the local namespace.

Expand Down Expand Up @@ -166,6 +170,8 @@ can be used in multi-line ``%%sql``:
FROM languages
```

+++

## Considerations

Because jupysql accepts `--`-delimited options like `--persist`, but `--`
Expand Down
52 changes: 52 additions & 0 deletions doc/user-guide/template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
jupytext:
text_representation:
extension: .md
format_name: myst
format_version: 0.13
jupytext_version: 1.14.5
kernelspec:
display_name: Python 3 (ipykernel)
language: python
name: python3
---

# Template

## Variable Expansion as `{{variable}}`

We support the variable expansion in the form of `{{variable}}`, this also allows the user to write the query as template with some dynamic variables

```{code-cell} ipython3
:tags: [remove-cell]

%load_ext sql
from pathlib import Path
from urllib.request import urlretrieve

if not Path("penguins.csv").is_file():
urlretrieve(
"https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv",
"penguins.csv",
)
%sql duckdb://
```

Now, let's give a simple query template and define some variables where we will apply in the template:

```{code-cell} ipython3
dynamic_limit = 5
dynamic_column = "island, sex"
```

```{code-cell} ipython3
%sql SELECT {{dynamic_column}} FROM penguins.csv LIMIT {{dynamic_limit}}
```

Note that variables will be fetched from the local namespace into the SQL statement.

Please aware that we also support the `$variable` or `{variable_name}` way, but those will be deprecated in future version, [see more](https://jupysql.ploomber.io/en/latest/intro.html?highlight=variable#variable-substitution).

```{code-cell} ipython3

```
37 changes: 31 additions & 6 deletions src/sql/command.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import re
import warnings
from IPython.core.magic_arguments import parse_argstring
from jinja2 import Template

from sqlalchemy.engine import Engine

Expand All @@ -19,12 +22,9 @@ class SQLCommand:
"""

def __init__(self, magic, user_ns, line, cell) -> None:
# Parse variables (words wrapped in {}) for %%sql magic
# (for %sql this is done automatically)
cell = magic.shell.var_expand(cell)

# Support for the variable substition in the SQL clause
line, cell = self._var_expand(magic, user_ns, line, cell)
self.args = parse.magic_args(magic.execute, line)

# self.args.line (everything that appears after %sql/%%sql in the first line)
# is splited in tokens (delimited by spaces), this checks if we have one arg
one_arg = len(self.args.line) == 1
Expand All @@ -45,7 +45,6 @@ def __init__(self, magic, user_ns, line, cell) -> None:
add_alias = True
else:
add_alias = False

self.command_text = " ".join(line_for_command) + "\n" + cell

if self.args.file:
Expand Down Expand Up @@ -89,3 +88,29 @@ def connection(self):
def result_var(self):
"""Returns the result_var"""
return self.parsed["result_var"]

def _var_expand(self, magic, user_ns, line, cell):
"""
Support for the variable substition in the SQL clause
For now, we have enabled two ways:
1. Latest format, {{a}}, we use jinja2 to parse the string with {{a}} format
2. Legacy format, {a}, $a, and :a format.

We will deprecate the legacy format feature in next major version
"""
self.is_legacy_var_expand_parsed = False
# Latest format parsing
# TODO: support --param and --use-global logic here
tonykploomber marked this conversation as resolved.
Show resolved Hide resolved
# Ref: https://github.com/ploomber/jupysql/issues/93
line = Template(line).render(user_ns)
cell = Template(cell).render(user_ns)
# Legacy format parsing
parsed_cell = magic.shell.var_expand(cell, depth=2)
parsed_line = magic.shell.var_expand(line, depth=2)
edublancas marked this conversation as resolved.
Show resolved Hide resolved
# Exclusive the string with "://", but has :variable
has_SQLAlchemy_var_expand = re.search("(?<!://):[^/]+", line) or ":" in cell
edublancas marked this conversation as resolved.
Show resolved Hide resolved
if parsed_line != line or parsed_cell != cell or has_SQLAlchemy_var_expand:
self.is_legacy_var_expand_parsed = True
warnings.warn("Please aware the variable substition. Use {{a}} instead"
, FutureWarning)
return parsed_line, parsed_cell
3 changes: 3 additions & 0 deletions src/sql/magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
line_magic,
magics_class,
needs_local_scope,
no_var_expand,
)
from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring
from sqlalchemy.exc import OperationalError, ProgrammingError, DatabaseError
Expand All @@ -19,6 +20,7 @@
from sql.magic_plot import SqlPlotMagic
from sql.magic_cmd import SqlCmdMagic


from traitlets.config.configurable import Configurable
from traitlets import Bool, Int, Unicode, observe

Expand Down Expand Up @@ -134,6 +136,7 @@ def _mutex_autopandas_autopolars(self, change):
setattr(self, other, False)
print(f"Disabled '{other}' since '{change['name']}' was enabled.")

@no_var_expand
@needs_local_scope
@line_magic("sql")
@cell_magic("sql")
Expand Down
83 changes: 82 additions & 1 deletion src/tests/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,47 @@ def test_parse_sql_when_passing_engine(ip, sql_magic, tmp_empty, line):
assert cmd.sql_original == sql_expected


def test_variable_substitution_cell_magic(ip, sql_magic):
def test_variable_substitution_legacy_warning_message_dollar_prefix(
ip, sql_magic, capsys
):
with pytest.warns(FutureWarning):
ip.user_global_ns["limit_number"] = 1
ip.run_cell_magic(
"sql",
"",
"""
SELECT * FROM author LIMIT $limit_number
""",
)


def test_variable_substitution_legacy_warning_message_single_curly(
ip, sql_magic, capsys
):
with pytest.warns(FutureWarning):
ip.user_global_ns["limit_number"] = 1
ip.run_cell_magic(
"sql",
"",
"""
SELECT * FROM author LIMIT {limit_number}
""",
)


def test_variable_substitution_legacy_warning_message_colon(ip, sql_magic, capsys):
with pytest.warns(FutureWarning):
ip.user_global_ns["limit_number"] = 1
ip.run_cell_magic(
"sql",
"",
"""
SELECT * FROM author LIMIT :limit_number
""",
)


def test_variable_substitution_legacy_dollar_prefix_cell_magic(ip, sql_magic):
ip.user_global_ns["username"] = "some-user"

cmd = SQLCommand(
Expand All @@ -181,3 +221,44 @@ def test_variable_substitution_cell_magic(ip, sql_magic):
)

assert cmd.parsed["sql"] == "\nGRANT CONNECT ON DATABASE postgres TO some-user;"


def test_variable_substitution_legacy_single_curly_cell_magic(ip, sql_magic):
ip.user_global_ns["username"] = "some-user"

cmd = SQLCommand(
sql_magic,
ip.user_ns,
line="",
cell="GRANT CONNECT ON DATABASE postgres TO {username};",
)

assert cmd.parsed["sql"] == "\nGRANT CONNECT ON DATABASE postgres TO some-user;"


def test_variable_substitution_double_curly_cell_magic(ip, sql_magic):
ip.user_global_ns["username"] = "some-user"

cmd = SQLCommand(
sql_magic,
ip.user_ns,
line="",
cell="GRANT CONNECT ON DATABASE postgres TO {{username}};",
)

print("cmd.parsed['sql']", cmd.parsed["sql"])
assert cmd.parsed["sql"] == "\nGRANT CONNECT ON DATABASE postgres TO some-user;"


def test_variable_substitution_double_curly_line_magic(ip, sql_magic):
ip.user_global_ns["limit_number"] = 5
ip.user_global_ns["column_name"] = "first_name"
cmd = SQLCommand(
sql_magic,
ip.user_ns,
line="SELECT {{column_name}} FROM author LIMIT {{limit_number}};",
cell="",
)

# print ("cmd.parsed['sql']", cmd.parsed["sql"])
assert cmd.parsed["sql"] == "SELECT first_name FROM author LIMIT 5;\n"