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

658 snippet support sqlcmd #721

Closed
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
a40bd59
snippet support for profile and explore
AnirudhVIyer Jul 11, 2023
120ccdb
snippet support for profile and explore 1
AnirudhVIyer Jul 11, 2023
f2deca6
Update table_explorer.ipynb
AnirudhVIyer Jul 11, 2023
e9251f7
Merge branch 'master' into 658-snippet-support-sqlcmd
AnirudhVIyer Jul 12, 2023
fc11cc4
review changes 2
AnirudhVIyer Jul 12, 2023
ddb7cbf
Update table_explorer.ipynb
AnirudhVIyer Jul 12, 2023
44bdcda
fix lint
AnirudhVIyer Jul 12, 2023
62c5b83
Merge branch '658-snippet-support-sqlcmd' of https://github.com/Aniru…
AnirudhVIyer Jul 12, 2023
023d366
fix errors
AnirudhVIyer Jul 12, 2023
901a2c4
fix errors review
AnirudhVIyer Jul 12, 2023
e9aabba
modyfying snippet message
AnirudhVIyer Jul 13, 2023
aea58f5
refactored is_snippet and with-clause
AnirudhVIyer Jul 14, 2023
ed2ff7c
refactored is_snippet and with-clause removed
AnirudhVIyer Jul 14, 2023
466178d
Merge branch 'ploomber:master' into 658-snippet-support-sqlcmd
AnirudhVIyer Jul 14, 2023
7276af3
is_snippet refactor, remove with_clause
AnirudhVIyer Jul 14, 2023
49ca7a7
Merge branch '658-snippet-support-sqlcmd' of https://github.com/Aniru…
AnirudhVIyer Jul 14, 2023
2f53599
Empty-Commit
AnirudhVIyer Jul 14, 2023
44652df
use dictionary to select messaged
AnirudhVIyer Jul 17, 2023
2651854
use dictionary to select messages
AnirudhVIyer Jul 17, 2023
9322172
use dictionary to select messages 1
AnirudhVIyer Jul 17, 2023
2d34975
Merge branch 'master' into 658-snippet-support-sqlcmd
AnirudhVIyer Jul 17, 2023
a677d8b
use dictionary to select messages 2
AnirudhVIyer Jul 17, 2023
0b2069d
Update duckdb-native-sqlalchemy.md
AnirudhVIyer Jul 17, 2023
7a6ec04
use dictionary to select messages 3
AnirudhVIyer Jul 17, 2023
6920d50
replace store_query with prep_query
AnirudhVIyer Jul 18, 2023
0c235df
replace store_query with prep_query 1
AnirudhVIyer Jul 18, 2023
40a63e9
Merge branch 'master' into 658-snippet-support-sqlcmd
AnirudhVIyer Jul 18, 2023
8abe684
Update duckdb-native-sqlalchemy.md
AnirudhVIyer Jul 18, 2023
f4e7e86
replace store_query with prep_query 2
AnirudhVIyer Jul 18, 2023
ae843a6
replace store_query with prep_query 3
AnirudhVIyer Jul 18, 2023
040f9eb
replace store_query with prep_query 4
AnirudhVIyer Jul 18, 2023
6d6b6d3
replace store_query with prep_query 5
AnirudhVIyer Jul 18, 2023
f391844
restore store_render
AnirudhVIyer Jul 19, 2023
5d3fbbb
restore prepare_query
AnirudhVIyer Jul 19, 2023
f612d15
Merge branch 'master' into 658-snippet-support-sqlcmd
AnirudhVIyer Jul 20, 2023
839224e
integration test added
AnirudhVIyer Jul 20, 2023
8919b22
Merge branch 'master' into 658-snippet-support-sqlcmd
AnirudhVIyer Jul 27, 2023
4f98ffd
refactor changes
AnirudhVIyer Jul 27, 2023
1e044ac
resolve merge conflicts
AnirudhVIyer Jul 27, 2023
46189c0
resolve merge conflicts 1
AnirudhVIyer Jul 27, 2023
b05c54e
resolve merge conflicts 2
AnirudhVIyer Jul 27, 2023
9fe8de6
resolve merge conflicts 3
AnirudhVIyer Jul 27, 2023
8507eca
Merge branch 'master' into 658-snippet-support-sqlcmd
AnirudhVIyer Jul 27, 2023
056c876
resolve merge conflicts 3
AnirudhVIyer Jul 27, 2023
04b77a3
merge conflict 4
AnirudhVIyer Jul 27, 2023
213757f
changes
AnirudhVIyer Jul 31, 2023
0194b32
Merge branch 'master' into 658-snippet-support-sqlcmd
AnirudhVIyer Aug 4, 2023
f0feedb
update tests
AnirudhVIyer Aug 7, 2023
e3d2889
update tests
AnirudhVIyer Aug 7, 2023
b28fed3
test commit
AnirudhVIyer Aug 8, 2023
e1049c1
change integration test
AnirudhVIyer Aug 8, 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 @@ -13,9 +13,11 @@
* [Fix] Refactored `ResultSet` to lazy loading (#470)
* [Fix] Removed `WITH` when a snippet does not have a dependency (#657)
* [Fix] Used display module when generating CTE (#649)
* [Fix] Added support for `profile` and `explore` commands with saved snippets (#658)
* [Doc] Re-organized sections. Adds section showing how to share notebooks via Ploomber Cloud
* [Fix] Adding `--with` back because of issues with sqlglot query parser (#684)


## 0.7.9 (2023-06-19)

* [Feature] Modified `histogram` command to support data with NULL values ([#176](https://github.com/ploomber/jupysql/issues/176))
Expand Down
30 changes: 21 additions & 9 deletions src/sql/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from sql import util
from IPython.core.display import HTML
import uuid
from sql.store import store


def _get_inspector(conn):
Expand Down Expand Up @@ -173,7 +174,7 @@ class Columns(DatabaseInspection):
"""

def __init__(self, name, schema, conn=None) -> None:
util.is_table_exists(name, schema)
util.is_saved_snippet_or_table_exists(name, schema)

inspector = _get_inspector(conn)

Expand Down Expand Up @@ -231,14 +232,20 @@ class TableDescription(DatabaseInspection):
"""

def __init__(self, table_name, schema=None) -> None:
util.is_table_exists(table_name, schema)
with_ = util.is_saved_snippet_or_table_exists(table_name, schema)

if schema:
table_name = f"{schema}.{table_name}"

if with_:
with_clause = str(store.render("", with_=with_))
neelasha23 marked this conversation as resolved.
Show resolved Hide resolved
else:
with_clause = ""

columns_query_result = sql.run.raw_run(
Connection.current, f"SELECT * FROM {table_name} WHERE 1=0"
Connection.current, f"{with_clause} SELECT * FROM {table_name} WHERE 1=0"
)

if Connection.is_custom_connection():
columns = [i[0] for i in columns_query_result.description]
else:
Expand All @@ -255,7 +262,8 @@ def __init__(self, table_name, schema=None) -> None:
# check the datatype of a column
try:
result = sql.run.raw_run(
Connection.current, f"""SELECT {column} FROM {table_name} LIMIT 1"""
Connection.current,
f"""{with_clause} SELECT {column} FROM {table_name} LIMIT 1""",
).fetchone()

value = result[0]
Expand All @@ -272,7 +280,7 @@ def __init__(self, table_name, schema=None) -> None:
try:
result_col_freq_values = sql.run.raw_run(
Connection.current,
f"""SELECT DISTINCT {column} as top,
f"""{with_clause} SELECT DISTINCT {column} as top,
COUNT({column}) as frequency FROM {table_name}
GROUP BY top ORDER BY frequency Desc""",
).fetchall()
Expand All @@ -290,6 +298,7 @@ def __init__(self, table_name, schema=None) -> None:
result_value_values = sql.run.raw_run(
Connection.current,
f"""
{with_clause}
SELECT MIN({column}) AS min,
MAX({column}) AS max,
COUNT({column}) AS count
Expand All @@ -314,6 +323,7 @@ def __init__(self, table_name, schema=None) -> None:
result_value_values = sql.run.raw_run(
Connection.current,
f"""
{with_clause}
SELECT
COUNT(DISTINCT {column}) AS unique_count
FROM {table_name}
Expand All @@ -329,10 +339,11 @@ def __init__(self, table_name, schema=None) -> None:
results_avg = sql.run.raw_run(
Connection.current,
f"""
SELECT AVG({column}) AS avg
FROM {table_name}
WHERE {column} IS NOT NULL
""",
{with_clause}
SELECT AVG({column}) AS avg
FROM {table_name}
WHERE {column} IS NOT NULL
""",
).fetchall()

columns_to_include_in_report.update(["mean"])
Expand All @@ -349,6 +360,7 @@ def __init__(self, table_name, schema=None) -> None:
result = sql.run.raw_run(
Connection.current,
f"""
{with_clause}
SELECT
stddev_pop({column}) as key_std,
percentile_disc(0.25) WITHIN GROUP
Expand Down
4 changes: 2 additions & 2 deletions src/sql/magic_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def execute(self, line="", cell="", local_ns=None):
if cmd.args.with_:
with_ = cmd.args.with_
else:
with_ = self._check_table_exists(table)
with_ = self.is_saved_snippet_or_table_exists(table)

if cmd.args.line[0] in {"box", "boxplot"}:
return plot.boxplot(
Expand Down Expand Up @@ -129,7 +129,7 @@ def execute(self, line="", cell="", local_ns=None):
)

@staticmethod
def _check_table_exists(table):
def is_saved_snippet_or_table_exists(table):
with_ = None
if util.is_saved_snippet(table):
with_ = [table]
Expand Down
35 changes: 30 additions & 5 deletions src/sql/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ def support_only_sql_alchemy_connection(command):


def fetch_sql_with_pagination(
table, offset, n_rows, sort_column=None, sort_order=None
table, offset, n_rows, sort_column=None, sort_order=None, with_clause=None
) -> tuple:
"""
Returns next n_rows and columns from table starting at the offset
Expand All @@ -277,19 +277,25 @@ def fetch_sql_with_pagination(

sort_order : 'DESC' or 'ASC', default None
Order list

with_clause : str, default None
Name of the snippet used to generate the table
"""
is_table_exists(table)

order_by = "" if not sort_column else f"ORDER BY {sort_column} {sort_order}"
if with_clause is None:
with_clause = ""
is_table_exists(table)

order_by = "" if not sort_column else f"ORDER BY {sort_column} {sort_order}"
query = f"""
{with_clause}
SELECT * FROM {table} {order_by}
OFFSET {offset} ROWS FETCH NEXT {n_rows} ROWS ONLY"""
LIMIT {n_rows} OFFSET {offset}"""
yafimvo marked this conversation as resolved.
Show resolved Hide resolved

rows = Connection.current.execute(query).fetchall()

columns = sql.run.raw_run(
Connection.current, f"SELECT * FROM {table} WHERE 1=0"
Connection.current, f"{with_clause} SELECT * FROM {table} WHERE 1=0"
).keys()

return rows, columns
Expand Down Expand Up @@ -363,3 +369,22 @@ def show_deprecation_warning():
"raise an exception in the next major release so please remove it.",
FutureWarning,
)


def is_saved_snippet_or_table_exists(table, schema=None):
"""
Check if the referenced table is a snippet
neelasha23 marked this conversation as resolved.
Show resolved Hide resolved
Parameters
----------
table : str, name of table
Returns
-------
with_ : str, default None
name of the snippet
"""
with_ = None
if is_saved_snippet(table):
with_ = [table]
else:
is_table_exists(table, schema)
return with_
17 changes: 13 additions & 4 deletions src/sql/widgets/table_widget/table_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
from sql.util import (
fetch_sql_with_pagination,
parse_sql_results_to_json,
is_table_exists,
is_saved_snippet_or_table_exists,
)
from sql.store import store

from sql.widgets import utils
from sql.telemetry import telemetry
Expand All @@ -33,7 +34,12 @@ def __init__(self, table):

self.html = ""

is_table_exists(table)
self.with_ = is_saved_snippet_or_table_exists(table)

if self.with_:
self.with_clause = str(store.render("", with_=self.with_))
else:
self.with_clause = ""

# load css
html_style = utils.load_css(f"{BASE_DIR}/css/tableWidget.css")
Expand All @@ -58,10 +64,12 @@ def create_table(self, table):
Creates an HTML table with default data
"""
rows_per_page = 10
rows, columns = fetch_sql_with_pagination(table, 0, rows_per_page)
rows, columns = fetch_sql_with_pagination(
table, 0, rows_per_page, with_clause=self.with_clause
)
rows = parse_sql_results_to_json(rows, columns)

query = f"SELECT count(*) FROM {table}"
query = f"{self.with_clause} SELECT count(*) FROM {table}"
n_total = Connection.current.session.execute(
sqlalchemy.sql.text(query)
).fetchone()[0]
Expand Down Expand Up @@ -187,6 +195,7 @@ def _recv(msg):
n_rows,
sort_column=sort_column,
sort_order=sort_order,
with_clause=self.with_clause,
)
rows_json = parse_sql_results_to_json(rows, columns)

Expand Down
90 changes: 90 additions & 0 deletions src/tests/test_magic_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from sql.store import store
from sql.inspect import _is_numeric
from sql.display import Table, Message
from IPython.utils.io import capture_output


VALID_COMMANDS_MESSAGE = (
Expand Down Expand Up @@ -391,6 +392,63 @@ def test_table_profile_store(ip, tmp_empty):
assert report.is_file()


def test_table_profile_with_snippets(ip, tmp_empty):
edublancas marked this conversation as resolved.
Show resolved Hide resolved
ip.run_cell(
"""
%%sql duckdb://
edublancas marked this conversation as resolved.
Show resolved Hide resolved
CREATE TABLE people (name varchar(50), number int, country varchar(50));
INSERT INTO people VALUES ('joe', 82, 'usa');
INSERT INTO people VALUES ('paula', 93, 'uk');
INSERT INTO people VALUES ('sam', 77, 'canada');
INSERT INTO people VALUES ('emily', 65, 'usa');
INSERT INTO people VALUES ('michael', 89, 'usa');
INSERT INTO people VALUES ('sarah', 81, 'uk');
INSERT INTO people VALUES ('james', 76, 'usa');
INSERT INTO people VALUES ('angela', 88, 'usa');
INSERT INTO people VALUES ('robert', 82, 'usa');
INSERT INTO people VALUES ('lisa', 92, 'uk');
INSERT INTO people VALUES ('mark', 77, 'usa');
INSERT INTO people VALUES ('jennifer', 68, 'australia');
"""
)
ip.run_cell(
"""
%%sql --save citizen
select * from people where country = 'usa'
"""
)
out = ip.run_cell("%sqlcmd profile -t citizen").result

expected = {
"count": [7, 7, 7],
"mean": [math.nan, "79.8571", math.nan],
"min": [math.nan, 65, math.nan],
"max": [math.nan, 89, math.nan],
"unique": [7, 6, 1],
"freq": [1, math.nan, 7],
"top": ["joe", math.nan, "usa"],
"std": [math.nan, "7.5862", math.nan],
"25%": [math.nan, "76.0000", math.nan],
"50%": [math.nan, "82.0000", math.nan],
"75%": [math.nan, "88.0000", math.nan],
}

stats_table = out._table

assert len(stats_table.rows) == len(expected)

for row in stats_table:
profile_metric = _get_row_string(row, " ")
name = _get_row_string(row, "name")
number = _get_row_string(row, "number")
country = _get_row_string(row, "country")

assert profile_metric in expected
assert name == str(expected[profile_metric][0])
assert number == str(expected[profile_metric][1])
assert country == str(expected[profile_metric][2])
Comment on lines +431 to +440

Choose a reason for hiding this comment

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

keep tests simples, using for or if makes them harder to understand. try simplifying this while removing this for loop



@pytest.mark.parametrize(
"cell, error_type, error_message",
[
Expand Down Expand Up @@ -585,3 +643,35 @@ def test_delete_invalid_snippet(arg, ip_snippets):
str(out.error_in_exec) == "No such saved snippet found "
": non_existent_snippet"
)


def test_table_explore_with_snippets(ip, tmp_empty):
ip.run_cell(
"""
%%sql duckdb://
edublancas marked this conversation as resolved.
Show resolved Hide resolved
CREATE TABLE people (name varchar(50), number int, country varchar(50));
INSERT INTO people VALUES ('joe', 82, 'usa');
INSERT INTO people VALUES ('paula', 93, 'uk');
INSERT INTO people VALUES ('sam', 77, 'canada');
INSERT INTO people VALUES ('emily', 65, 'usa');
INSERT INTO people VALUES ('michael', 89, 'usa');
INSERT INTO people VALUES ('sarah', 81, 'uk');
INSERT INTO people VALUES ('james', 76, 'usa');
INSERT INTO people VALUES ('angela', 88, 'usa');
INSERT INTO people VALUES ('robert', 82, 'usa');
INSERT INTO people VALUES ('lisa', 92, 'uk');
INSERT INTO people VALUES ('mark', 77, 'usa');
INSERT INTO people VALUES ('jennifer', 68, 'australia');
"""
)
ip.run_cell(
"""
%%sql --save citizen
select * from people where country = 'usa'
"""
)
with capture_output() as captured:
ip.run_cell("%sqlcmd explore -t citizen")

assert "Plotting using saved snippet : citizen" in captured.stdout
neelasha23 marked this conversation as resolved.
Show resolved Hide resolved
assert "sql.widgets.table_widget.table_widget.TableWidget object" in captured.stdout
Loading