-
Notifications
You must be signed in to change notification settings - Fork 609
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(api): promote
psql
to a show_sql
public API
- Loading branch information
Showing
9 changed files
with
209 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import io | ||
|
||
import pytest | ||
from pytest import mark, param | ||
|
||
import ibis | ||
import ibis.common.exceptions as exc | ||
from ibis import _ | ||
|
||
sa = pytest.importorskip("sqlalchemy") | ||
pytest.importorskip("sqlglot") | ||
|
||
|
||
@mark.never( | ||
["dask", "pandas"], | ||
reason="Dask and Pandas are not SQL backends", | ||
raises=(NotImplementedError, AssertionError), | ||
) | ||
@mark.notimpl( | ||
["datafusion", "pyspark"], | ||
reason="Not clear how to extract SQL from the backend", | ||
raises=(exc.OperationNotDefinedError, NotImplementedError, AssertionError), | ||
) | ||
def test_table(con): | ||
expr = con.tables.functional_alltypes.select(c=_.int_col + 1) | ||
buf = io.StringIO() | ||
ibis.show_sql(expr, file=buf) | ||
assert buf.getvalue() | ||
|
||
|
||
simple_literal = param( | ||
ibis.literal(1), | ||
id="simple_literal", | ||
) | ||
array_literal = param( | ||
ibis.array([1]), | ||
marks=[ | ||
mark.never( | ||
["mysql", "sqlite"], | ||
raises=sa.exc.CompileError, | ||
reason="arrays not supported in the backend", | ||
), | ||
mark.notyet( | ||
["impala"], | ||
raises=NotImplementedError, | ||
reason="Impala hasn't implemented array literals", | ||
), | ||
mark.notimpl( | ||
["postgres"], | ||
reason="array literals are not yet implemented", | ||
raises=NotImplementedError, | ||
), | ||
], | ||
id="array_literal", | ||
) | ||
no_structs = mark.never( | ||
["impala", "mysql", "sqlite"], | ||
raises=(NotImplementedError, sa.exc.CompileError), | ||
reason="structs not supported in the backend", | ||
) | ||
no_struct_literals = mark.notimpl( | ||
["postgres"], | ||
reason="struct literals are not yet implemented", | ||
) | ||
not_sql = mark.never( | ||
["pandas", "dask"], | ||
raises=(exc.IbisError, NotImplementedError, AssertionError), | ||
reason="Not a SQL backend", | ||
) | ||
no_sql_extraction = mark.notimpl( | ||
["datafusion", "pyspark"], | ||
reason="Not clear how to extract SQL from the backend", | ||
) | ||
|
||
|
||
@mark.parametrize( | ||
"expr", | ||
[ | ||
simple_literal, | ||
array_literal, | ||
param( | ||
ibis.struct(dict(a=1)), | ||
marks=[no_structs, no_struct_literals], | ||
id="struct_literal", | ||
), | ||
], | ||
) | ||
@not_sql | ||
@no_sql_extraction | ||
def test_literal(backend, expr): | ||
buf = io.StringIO() | ||
ibis.show_sql(expr, dialect=backend.name(), file=buf) | ||
assert buf.getvalue() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
from __future__ import annotations | ||
|
||
from typing import IO | ||
|
||
import ibis | ||
import ibis.common.exceptions as com | ||
import ibis.expr.types as ir | ||
|
||
_IBIS_TO_SQLGLOT_NAME_MAP = { | ||
# not 100% accurate, but very close | ||
"impala": "hive", | ||
# for now map clickhouse to Hive so that _something_ works | ||
"clickhouse": "mysql", | ||
} | ||
|
||
|
||
def show_sql( | ||
expr: ir.Expr, | ||
dialect: str | None = None, | ||
file: IO[str] | None = None, | ||
) -> None: | ||
"""Pretty-print the compiled SQL string of an expression. | ||
If a dialect cannot be inferred and one was not passed, duckdb | ||
will be used as the dialect | ||
Parameters | ||
---------- | ||
expr | ||
Ibis expression whose SQL will be printed | ||
dialect | ||
String dialect. This is typically not required, but can be useful if | ||
ibis cannot infer the backend dialect. | ||
file | ||
File to write output to | ||
Examples | ||
-------- | ||
>>> import ibis | ||
>>> from ibis import _ | ||
>>> t = ibis.table(dict(a="int"), name="t") | ||
>>> expr = t.select(c=_.a * 2) | ||
>>> ibis.show_sql(expr) # duckdb dialect by default | ||
SELECT | ||
t0.a * CAST(2 AS SMALLINT) AS c | ||
FROM t AS t0 | ||
>>> ibis.show_sql(expr, dialect="mysql") | ||
SELECT | ||
t0.a * 2 AS c | ||
FROM t AS t0 | ||
""" | ||
import sqlglot | ||
|
||
# try to infer from a non-str expression or if not possible fallback to | ||
# the default pretty dialect for expressions | ||
if dialect is None: | ||
try: | ||
backend = expr._find_backend() | ||
except com.IbisError: | ||
# default to duckdb for sqlalchemy compilation because it supports | ||
# the widest array of ibis features for SQL backends | ||
read = "duckdb" | ||
write = ibis.options.sql.default_dialect | ||
else: | ||
read = write = backend.name | ||
else: | ||
read = write = dialect | ||
|
||
write = _IBIS_TO_SQLGLOT_NAME_MAP.get(write, write) | ||
|
||
try: | ||
compiled = expr.compile() | ||
except com.IbisError: | ||
backend = getattr(ibis, read) | ||
compiled = backend.compile(expr) | ||
try: | ||
sql = str(compiled.compile(compile_kwargs={"literal_binds": True})) | ||
except (AttributeError, TypeError): | ||
sql = compiled | ||
|
||
assert isinstance( | ||
sql, str | ||
), f"expected `str`, got `{sql.__class__.__name__}`" | ||
(pretty,) = sqlglot.transpile( | ||
sql, | ||
read=_IBIS_TO_SQLGLOT_NAME_MAP.get(read, read), | ||
write=write, | ||
pretty=True, | ||
) | ||
print(pretty, file=file) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.