Skip to content

Commit 4750d49

Browse files
Merge pull request #7581 from jorisvandenbossche/sql-reflect-database
SQL: don't reflect full database GH7396
2 parents f6a5f41 + 8131b20 commit 4750d49

File tree

2 files changed

+44
-18
lines changed

2 files changed

+44
-18
lines changed

pandas/io/sql.py

+26-18
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@ def _parse_date_columns(data_frame, parse_dates):
7676
return data_frame
7777

7878

79+
def _is_sqlalchemy_engine(con):
80+
try:
81+
import sqlalchemy
82+
if isinstance(con, sqlalchemy.engine.Engine):
83+
return True
84+
else:
85+
return False
86+
except ImportError:
87+
return False
88+
89+
7990
def execute(sql, con, cur=None, params=None):
8091
"""
8192
Execute the given SQL query using the provided connection object.
@@ -262,7 +273,15 @@ def read_sql_table(table_name, con, index_col=None, coerce_float=True,
262273
263274
264275
"""
265-
pandas_sql = PandasSQLAlchemy(con)
276+
import sqlalchemy
277+
from sqlalchemy.schema import MetaData
278+
meta = MetaData(con)
279+
try:
280+
meta.reflect(only=[table_name])
281+
except sqlalchemy.exc.InvalidRequestError:
282+
raise ValueError("Table %s not found" % table_name)
283+
284+
pandas_sql = PandasSQLAlchemy(con, meta=meta)
266285
table = pandas_sql.read_table(
267286
table_name, index_col=index_col, coerce_float=coerce_float,
268287
parse_dates=parse_dates, columns=columns)
@@ -380,6 +399,7 @@ def read_sql(sql, con, index_col=None, coerce_float=True, params=None,
380399
coerce_float=coerce_float, parse_dates=parse_dates)
381400

382401
if pandas_sql.has_table(sql):
402+
pandas_sql.meta.reflect(only=[sql])
383403
return pandas_sql.read_table(
384404
sql, index_col=index_col, coerce_float=coerce_float,
385405
parse_dates=parse_dates, columns=columns)
@@ -471,17 +491,9 @@ def pandasSQL_builder(con, flavor=None, meta=None, is_cursor=False):
471491
"""
472492
# When support for DBAPI connections is removed,
473493
# is_cursor should not be necessary.
474-
try:
475-
import sqlalchemy
476-
477-
if isinstance(con, sqlalchemy.engine.Engine):
478-
return PandasSQLAlchemy(con, meta=meta)
479-
else:
480-
if flavor == 'mysql':
481-
warnings.warn(_MYSQL_WARNING, FutureWarning)
482-
return PandasSQLLegacy(con, flavor, is_cursor=is_cursor)
483-
484-
except ImportError:
494+
if _is_sqlalchemy_engine(con):
495+
return PandasSQLAlchemy(con, meta=meta)
496+
else:
485497
if flavor == 'mysql':
486498
warnings.warn(_MYSQL_WARNING, FutureWarning)
487499
return PandasSQLLegacy(con, flavor, is_cursor=is_cursor)
@@ -768,7 +780,6 @@ def __init__(self, engine, meta=None):
768780
if not meta:
769781
from sqlalchemy.schema import MetaData
770782
meta = MetaData(self.engine)
771-
meta.reflect(self.engine)
772783

773784
self.meta = meta
774785

@@ -813,19 +824,16 @@ def tables(self):
813824
return self.meta.tables
814825

815826
def has_table(self, name):
816-
if self.meta.tables.get(name) is not None:
817-
return True
818-
else:
819-
return False
827+
return self.engine.has_table(name)
820828

821829
def get_table(self, table_name):
822830
return self.meta.tables.get(table_name)
823831

824832
def drop_table(self, table_name):
825833
if self.engine.has_table(table_name):
834+
self.meta.reflect(only=[table_name])
826835
self.get_table(table_name).drop()
827836
self.meta.clear()
828-
self.meta.reflect()
829837

830838
def _create_sql_schema(self, frame, table_name):
831839
table = PandasSQLTable(table_name, self, frame=frame)

pandas/io/tests/test_sql.py

+18
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,22 @@ def test_read_sql_delegate(self):
624624
iris_frame2 = sql.read_sql('iris', self.conn)
625625
tm.assert_frame_equal(iris_frame1, iris_frame2)
626626

627+
def test_not_reflect_all_tables(self):
628+
# create invalid table
629+
qry = """CREATE TABLE invalid (x INTEGER, y UNKNOWN);"""
630+
self.conn.execute(qry)
631+
qry = """CREATE TABLE other_table (x INTEGER, y INTEGER);"""
632+
self.conn.execute(qry)
633+
634+
with warnings.catch_warnings(record=True) as w:
635+
# Cause all warnings to always be triggered.
636+
warnings.simplefilter("always")
637+
# Trigger a warning.
638+
sql.read_sql_table('other_table', self.conn)
639+
sql.read_sql_query('SELECT * FROM other_table', self.conn)
640+
# Verify some things
641+
self.assertEqual(len(w), 0, "Warning triggered for other table")
642+
627643

628644
class TestSQLLegacyApi(_TestSQLApi):
629645
"""
@@ -737,6 +753,8 @@ def setup_connect(self):
737753
try:
738754
self.conn = self.connect()
739755
self.pandasSQL = sql.PandasSQLAlchemy(self.conn)
756+
# to test if connection can be made:
757+
self.conn.connect()
740758
except sqlalchemy.exc.OperationalError:
741759
raise nose.SkipTest("Can't connect to {0} server".format(self.flavor))
742760

0 commit comments

Comments
 (0)