Skip to content

Commit 9c10e35

Browse files
committed
fix(mssql): ensure that dots in database parameter to list_tables are used as path delineators
1 parent c99cc23 commit 9c10e35

File tree

9 files changed

+59
-35
lines changed

9 files changed

+59
-35
lines changed

ci/schema/mssql.sql

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
DROP TABLE IF EXISTS ibis_testing.dbo.diamonds;
1+
DROP TABLE IF EXISTS [ibis-testing].dbo.diamonds;
22

3-
CREATE TABLE ibis_testing.dbo.diamonds (
3+
CREATE TABLE [ibis-testing].dbo.diamonds (
44
carat FLOAT,
55
cut VARCHAR(MAX),
66
color VARCHAR(MAX),
@@ -17,13 +17,13 @@ CREATE TABLE ibis_testing.dbo.diamonds (
1717
-- /data is a volume mount to the ibis testing data
1818
-- used for snappy test data loading
1919
-- DataFrame.to_sql is unusably slow for loading CSVs
20-
BULK INSERT ibis_testing.dbo.diamonds
20+
BULK INSERT [ibis-testing].dbo.diamonds
2121
FROM '/data/diamonds.csv'
2222
WITH (FORMAT = 'CSV', FIELDTERMINATOR = ',', ROWTERMINATOR = '\n', FIRSTROW = 2)
2323

24-
DROP TABLE IF EXISTS ibis_testing.dbo.astronauts;
24+
DROP TABLE IF EXISTS [ibis-testing].dbo.astronauts;
2525

26-
CREATE TABLE ibis_testing.dbo.astronauts (
26+
CREATE TABLE [ibis-testing].dbo.astronauts (
2727
"id" BIGINT,
2828
"number" BIGINT,
2929
"nationwide_number" BIGINT,
@@ -50,13 +50,13 @@ CREATE TABLE ibis_testing.dbo.astronauts (
5050
"total_eva_hrs" DOUBLE PRECISION
5151
);
5252

53-
BULK INSERT ibis_testing.dbo.astronauts
53+
BULK INSERT [ibis-testing].dbo.astronauts
5454
FROM '/data/astronauts.csv'
5555
WITH (FORMAT = 'CSV', FIELDTERMINATOR = ',', ROWTERMINATOR = '\n', FIRSTROW = 2)
5656

57-
DROP TABLE IF EXISTS ibis_testing.dbo.batting;
57+
DROP TABLE IF EXISTS [ibis-testing].dbo.batting;
5858

59-
CREATE TABLE ibis_testing.dbo.batting (
59+
CREATE TABLE [ibis-testing].dbo.batting (
6060
"playerID" VARCHAR(MAX),
6161
"yearID" BIGINT,
6262
stint BIGINT,
@@ -81,13 +81,13 @@ CREATE TABLE ibis_testing.dbo.batting (
8181
"GIDP" BIGINT
8282
);
8383

84-
BULK INSERT ibis_testing.dbo.batting
84+
BULK INSERT [ibis-testing].dbo.batting
8585
FROM '/data/batting.csv'
8686
WITH (FORMAT = 'CSV', FIELDTERMINATOR = ',', ROWTERMINATOR = '\n', FIRSTROW = 2)
8787

88-
DROP TABLE IF EXISTS ibis_testing.dbo.awards_players;
88+
DROP TABLE IF EXISTS [ibis-testing].dbo.awards_players;
8989

90-
CREATE TABLE ibis_testing.dbo.awards_players (
90+
CREATE TABLE [ibis-testing].dbo.awards_players (
9191
"playerID" VARCHAR(MAX),
9292
"awardID" VARCHAR(MAX),
9393
"yearID" BIGINT,
@@ -96,13 +96,13 @@ CREATE TABLE ibis_testing.dbo.awards_players (
9696
notes VARCHAR(MAX)
9797
);
9898

99-
BULK INSERT ibis_testing.dbo.awards_players
99+
BULK INSERT [ibis-testing].dbo.awards_players
100100
FROM '/data/awards_players.csv'
101101
WITH (FORMAT = 'CSV', FIELDTERMINATOR = ',', ROWTERMINATOR = '\n', FIRSTROW = 2)
102102

103-
DROP TABLE IF EXISTS ibis_testing.dbo.functional_alltypes;
103+
DROP TABLE IF EXISTS [ibis-testing].dbo.functional_alltypes;
104104

105-
CREATE TABLE ibis_testing.dbo.functional_alltypes (
105+
CREATE TABLE [ibis-testing].dbo.functional_alltypes (
106106
id INTEGER,
107107
bool_col BIT,
108108
tinyint_col SMALLINT,
@@ -118,21 +118,21 @@ CREATE TABLE ibis_testing.dbo.functional_alltypes (
118118
month INTEGER
119119
);
120120

121-
BULK INSERT ibis_testing.dbo.functional_alltypes
121+
BULK INSERT [ibis-testing].dbo.functional_alltypes
122122
FROM '/data/functional_alltypes.csv'
123123
WITH (FORMAT = 'CSV', FIELDTERMINATOR = ',', ROWTERMINATOR = '\n', FIRSTROW = 2)
124124

125-
DROP TABLE IF EXISTS ibis_testing.dbo.win;
125+
DROP TABLE IF EXISTS [ibis-testing].dbo.win;
126126

127-
CREATE TABLE ibis_testing.dbo.win (g VARCHAR(MAX), x BIGINT NOT NULL, y BIGINT);
128-
INSERT INTO ibis_testing.dbo.win VALUES
127+
CREATE TABLE [ibis-testing].dbo.win (g VARCHAR(MAX), x BIGINT NOT NULL, y BIGINT);
128+
INSERT INTO [ibis-testing].dbo.win VALUES
129129
('a', 0, 3),
130130
('a', 1, 2),
131131
('a', 2, 0),
132132
('a', 3, 1),
133133
('a', 4, 1);
134134

135-
DROP TABLE IF EXISTS ibis_testing.dbo.topk;
135+
DROP TABLE IF EXISTS [ibis-testing].dbo.topk;
136136

137-
CREATE TABLE ibis_testing.dbo.topk (x BIGINT);
138-
INSERT INTO ibis_testing.dbo.topk VALUES (1), (1), (NULL);
137+
CREATE TABLE [ibis-testing].dbo.topk (x BIGINT);
138+
INSERT INTO [ibis-testing].dbo.topk VALUES (1), (1), (NULL);

compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ services:
6565
environment:
6666
MSSQL_SA_PASSWORD: 1bis_Testing!
6767
ACCEPT_EULA: "Y"
68-
# The default collation in MSSQL isS QL_Latin1_General_CP1_CI_AS
68+
# The default collation in MSSQL is SQL_Latin1_General_CP1_CI_AS
6969
# where the CI stands for Case Insensitive.
7070
# We use a case-sensitive collation for testing so that we don't
7171
# break users with case-sensitive collations.

ibis/backends/mssql/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def do_connect(
133133
>>> host = os.environ.get("IBIS_TEST_MSSQL_HOST", "localhost")
134134
>>> user = os.environ.get("IBIS_TEST_MSSQL_USER", "sa")
135135
>>> password = os.environ.get("IBIS_TEST_MSSQL_PASSWORD", "1bis_Testing!")
136-
>>> database = os.environ.get("IBIS_TEST_MSSQL_DATABASE", "ibis_testing")
136+
>>> database = os.environ.get("IBIS_TEST_MSSQL_DATABASE", "ibis-testing")
137137
>>> driver = os.environ.get("IBIS_TEST_MSSQL_PYODBC_DRIVER", "FreeTDS")
138138
>>> con = ibis.mssql.connect(
139139
... database=database,

ibis/backends/mssql/tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
MSSQL_PASS = os.environ.get("IBIS_TEST_MSSQL_PASSWORD", "1bis_Testing!")
1717
MSSQL_HOST = os.environ.get("IBIS_TEST_MSSQL_HOST", "localhost")
1818
MSSQL_PORT = int(os.environ.get("IBIS_TEST_MSSQL_PORT", 1433))
19-
IBIS_TEST_MSSQL_DB = os.environ.get("IBIS_TEST_MSSQL_DATABASE", "ibis_testing")
19+
IBIS_TEST_MSSQL_DB = os.environ.get("IBIS_TEST_MSSQL_DATABASE", "ibis-testing")
2020
MSSQL_PYODBC_DRIVER = os.environ.get("IBIS_TEST_MSSQL_PYODBC_DRIVER", "FreeTDS")
2121

2222

@@ -40,7 +40,7 @@ def postload(self, **kw: Any):
4040

4141
def _load_data(self, *, database: str = IBIS_TEST_MSSQL_DB, **_):
4242
with self.connection._safe_raw_sql(
43-
"IF DB_ID('ibis_testing') is NULL BEGIN CREATE DATABASE [ibis_testing] END"
43+
f"IF DB_ID('{database}') is NULL BEGIN CREATE DATABASE [{database}] END"
4444
):
4545
pass
4646

ibis/backends/mssql/tests/test_client.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,3 +333,10 @@ def test_mssql_without_password_is_valid():
333333
database=IBIS_TEST_MSSQL_DB,
334334
driver=MSSQL_PYODBC_DRIVER,
335335
)
336+
337+
338+
@pytest.mark.parametrize(
339+
"database", ["ibis-testing.dbo", ("ibis-testing", "dbo")], ids=["string", "tuple"]
340+
)
341+
def test_list_tables_with_dash(con, database):
342+
assert con.list_tables(database=database) == con.list_tables()

ibis/backends/oracle/__init__.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -298,12 +298,17 @@ def list_tables(
298298

299299
table_loc = self._to_sqlglot_table(table_loc)
300300

301+
dialect = self.dialect
302+
301303
# Deeply frustrating here where if we call `convert` on `table_loc`,
302304
# which is a sg.exp.Table, the quotes are rendered as double-quotes
303-
# which are invalid. With no `convert`, the same thing happens.
304-
# If we call `convert` on the stringified SQL output, they get reparsed
305-
# as literal strings and those are rendered correctly.
306-
conditions = C.owner.eq(sge.convert(table_loc.sql(self.name)))
305+
# which are invalid. So, we unquote the database name here.
306+
def unquote(node):
307+
if isinstance(node, sg.exp.Identifier):
308+
return sg.to_identifier(node.name, quoted=False)
309+
return node
310+
311+
conditions = C.owner.eq(sge.convert(table_loc.transform(unquote).sql(dialect)))
307312

308313
tables = (
309314
sg.select("table_name", "owner")
@@ -317,7 +322,7 @@ def list_tables(
317322
.distinct()
318323
.where(conditions)
319324
)
320-
sql = tables.union(views).sql(self.name)
325+
sql = tables.union(views).sql(dialect)
321326

322327
with self._safe_raw_sql(sql) as cur:
323328
out = cur.fetchall()

ibis/backends/sql/__init__.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,9 @@ def _to_catalog_db_tuple(self, table_loc: sge.Table):
607607
return sg_cat, sg_db
608608

609609
def _to_sqlglot_table(self, database):
610+
quoted = self.compiler.quoted
611+
dialect = self.dialect
612+
610613
if database is None:
611614
# Create "table" with empty catalog and db
612615
database = sge.Table(catalog=None, db=None)
@@ -629,19 +632,26 @@ def _to_sqlglot_table(self, database):
629632
'\n("database",)'
630633
)
631634
database = sge.Table(
632-
catalog=sg.to_identifier(catalog, quoted=self.compiler.quoted),
633-
db=sg.to_identifier(database, quoted=self.compiler.quoted),
635+
catalog=sg.to_identifier(catalog, quoted=quoted),
636+
db=sg.to_identifier(database, quoted=quoted),
634637
)
635638
elif isinstance(database, str):
636639
# There is no definition of a sqlglot catalog.database hierarchy outside
637640
# of the standard table expression.
638641
# sqlglot parsing of the string will assume that it's a Table
639642
# so we unpack the arguments into a new sqlglot object, switching
640643
# table (this) -> database (db) and database (db) -> catalog
641-
table = sg.parse_one(database, into=sge.Table, dialect=self.dialect)
644+
table = sg.parse_one(
645+
".".join(
646+
sg.to_identifier(part, quoted=quoted).sql(dialect)
647+
for part in database.split(".")
648+
),
649+
into=sge.Table,
650+
dialect=dialect,
651+
)
642652
if table.args["catalog"] is not None:
643653
raise exc.IbisInputError(
644-
f"Overspecified table hierarchy provided: `{table.sql(self.dialect)}`"
654+
f"Overspecified table hierarchy provided: `{table.sql(dialect)}`"
645655
)
646656
catalog = table.args["db"]
647657
db = table.args["this"]

ibis/backends/tests/test_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ def test_create_table_from_schema(con, new_schema, temp_table):
317317

318318
@mark.notimpl(
319319
["oracle"],
320+
raises=AssertionError,
320321
reason="oracle temp tables aren't cleaned up on reconnect -- they need to "
321322
"be switched from using atexit to weakref.finalize",
322323
)
@@ -669,7 +670,7 @@ def test_list_catalogs(con):
669670
"duckdb": {"memory"},
670671
"exasol": set(),
671672
"flink": set(),
672-
"mssql": {"ibis_testing"},
673+
"mssql": {"ibis-testing"},
673674
"oracle": set(),
674675
"postgres": {"postgres", "ibis_testing"},
675676
"risingwave": {"dev"},

justfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ down *backends:
211211
# stop all containers, prune networks, and remove all volumes
212212
stop *backends:
213213
just down {{ backends }}
214+
docker system prune --force --volumes
214215
docker network prune -f
215216
docker volume prune -af
216217

0 commit comments

Comments
 (0)