Skip to content

Commit 43a8690

Browse files
committed
Quote view names in unregister
1 parent 0bc5571 commit 43a8690

File tree

2 files changed

+34
-1
lines changed

2 files changed

+34
-1
lines changed

src/duckdb_py/pyconnection.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1776,7 +1776,8 @@ shared_ptr<DuckDBPyConnection> DuckDBPyConnection::UnregisterPythonObject(const
17761776
D_ASSERT(py::gil_check());
17771777
py::gil_scoped_release release;
17781778
// FIXME: DROP TEMPORARY VIEW? doesn't exist?
1779-
connection.Query("DROP VIEW \"" + name + "\"");
1779+
const auto quoted_name = KeywordHelper::WriteOptionallyQuoted(name, '\"');
1780+
connection.Query("DROP VIEW " + quoted_name + "");
17801781
registered_objects.erase(name);
17811782
return shared_from_this();
17821783
}

tests/fast/api/test_duckdb_connection.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,38 @@ def test_unregister_problematic_behavior(self, duckdb_cursor):
313313
# This should not have affected the existing view:
314314
assert duckdb_cursor.execute("select * from vw").fetchone() == (0,)
315315

316+
def test_unregister_quoted_table_names(self, duckdb_cursor):
317+
"""Test that unregister works for quoted tables."""
318+
rel = duckdb_cursor.sql("select 'test', 'data'")
319+
320+
table_name = 'test with .s and "s and s'
321+
duckdb_cursor.register(table_name, rel)
322+
duckdb_cursor.unregister(table_name)
323+
324+
escaped_table_name = table_name.replace('"', '""')
325+
with pytest.raises(duckdb.CatalogException):
326+
duckdb_cursor.sql(f'select * from "{escaped_table_name}"')
327+
328+
def test_unregister_with_scary_name(self, duckdb_cursor):
329+
"""Test that unregister doesn't have side effects."""
330+
rel = duckdb_cursor.sql("select 'test', 'data'")
331+
332+
scary_name = 'test";create table foo as select * from range(10);--'
333+
# make sure a view with the name "test" exists
334+
duckdb_cursor.register("test", rel)
335+
duckdb_cursor.register(scary_name, rel)
336+
# try to trick unregister (which uses DROP VIEW) to run another statement
337+
duckdb_cursor.unregister(scary_name)
338+
339+
# hopefully that didn't happen
340+
with pytest.raises(duckdb.CatalogException):
341+
duckdb_cursor.sql("select * from foo")
342+
343+
# verify the scary name table was properly unregistered
344+
escaped_scary_name = scary_name.replace('"', '""')
345+
with pytest.raises(duckdb.CatalogException):
346+
duckdb_cursor.sql(f'select * from "{escaped_scary_name}"')
347+
316348
@pytest.mark.parametrize("pandas", [NumpyPandas(), ArrowPandas()])
317349
def test_relation_out_of_scope(self, pandas):
318350
def temporary_scope():

0 commit comments

Comments
 (0)