Repository: pandas-dev/pandas

Issue:
Support sqlalchemy.Executable in pandas.read_sql(sql)
Since pandas.read_sql() can always use sqlalchemy.Engine or Connection, it would be great if the sql parameter can accept sqlalchemy.Executable in addition to a string. sqlalchemy.Executable provides a standardized way of binding parameters and generating sql for different kinds of DB.  



Discussion:
I think this is a good idea. 

In practice, wouldn't this only be a `Select` object? As it only makes sense for Executables that return values to provide this to `read_sql`, and other Executables as insert, update, delete don't do this.

Yes it should only be a Select or Text object. I especially like sqlalchemy's way to standardize parameters with text(). For example, I can just write text('select \* from tbl where name=:value') regardless of actual backends. 

I'm going to look at this for EuroSciPy sprints.  Maybe get some tests in place at least.


Resolving patch:
diff --git a/pandas/io/sql.py b/pandas/io/sql.py
--- a/pandas/io/sql.py
+++ b/pandas/io/sql.py
@@ -364,9 +364,9 @@ def read_sql_query(sql, con, index_col=None, coerce_float=True, params=None,
 
     Parameters
     ----------
-    sql : string
-        SQL query to be executed
-    con : SQLAlchemy connectable(engine/connection) or database string URI
+    sql : string SQL query or SQLAlchemy Selectable (select or text object)
+        to be executed.
+    con : SQLAlchemy connectable(engine/connection) or database string URI 
         or sqlite3 DBAPI2 connection
         Using SQLAlchemy makes it possible to use any DB supported by that
         library.
@@ -423,8 +423,8 @@ def read_sql(sql, con, index_col=None, coerce_float=True, params=None,
 
     Parameters
     ----------
-    sql : string
-        SQL query to be executed or database table name.
+    sql : string SQL query or SQLAlchemy Selectable (select or text object)
+        to be executed, or database table name.
     con : SQLAlchemy connectable(engine/connection) or database string URI
         or DBAPI2 connection (fallback mode)
         Using SQLAlchemy makes it possible to use any DB supported by that


Test patch:
diff --git a/pandas/io/tests/test_sql.py b/pandas/io/tests/test_sql.py
--- a/pandas/io/tests/test_sql.py
+++ b/pandas/io/tests/test_sql.py
@@ -951,6 +951,35 @@ def test_to_sql_read_sql_with_database_uri(self):
         tm.assert_frame_equal(test_frame1, test_frame3)
         tm.assert_frame_equal(test_frame1, test_frame4)
 
+    def _make_iris_table_metadata(self):
+        sa = sqlalchemy
+        metadata = sa.MetaData()
+        iris = sa.Table('iris', metadata,
+            sa.Column('SepalLength', sa.REAL),
+            sa.Column('SepalWidth', sa.REAL),
+            sa.Column('PetalLength', sa.REAL),
+            sa.Column('PetalWidth', sa.REAL),
+            sa.Column('Name', sa.TEXT)
+        )
+
+        return iris
+
+    def test_query_by_text_obj(self):
+        # WIP : GH10846
+        name_text = sqlalchemy.text('select * from iris where name=:name')
+        iris_df = sql.read_sql(name_text, self.conn, params={'name': 'Iris-versicolor'})
+        all_names = set(iris_df['Name'])
+        self.assertEqual(all_names, set(['Iris-versicolor']))
+
+    def test_query_by_select_obj(self):
+        # WIP : GH10846
+        iris = self._make_iris_table_metadata()
+
+        name_select = sqlalchemy.select([iris]).where(iris.c.Name == sqlalchemy.bindparam('name'))
+        iris_df = sql.read_sql(name_select, self.conn, params={'name': 'Iris-setosa'})
+        all_names = set(iris_df['Name'])
+        self.assertEqual(all_names, set(['Iris-setosa']))
+
 
 class _EngineToConnMixin(object):
     """