Repository: dask/dask

Issue:
read_sql_table creates delayed objects without provided sqlalchemy connection arguments
I am trying to load a table from presto using Dask `read_sql_table`. The presto server runs on `https`. As per the [pyhive docs](https://github.com/dropbox/PyHive#passing-session-configuration) if server is on SSL you have to pass `{'engine_kwargs': {'connect_args': {'protocol': 'https'}}}`. 

Code:
```python
import dask.dataframe as dd
df = read_sql_table('test_extension', 'presto://server_url/hive',
                      index_col='col_name', divisions=[0, 30000, 60000], head_rows=100,
                       engine_kwargs={'connect_args': {'protocol': 'https'}})
# till here everything is fine it extracts metadata , creates correct number of partitions.
df.head()
```

`df.head` fails saying an http request was made to an https endpoint. I checked this using a debugger as well and the request is made with http protocol. 

I suspect the problem is at https://github.com/dask/dask/blob/6fd2eb57158dbf260e29311e84f6a389904f0ef2/dask/dataframe/io/sql.py#L191, this does not pass engine kwargs to https://github.com/dask/dask/blob/6fd2eb57158dbf260e29311e84f6a389904f0ef2/dask/dataframe/io/sql.py#L196

If the server is on http and I change protocol param to http then everything works.


Discussion:
How would you write the equivalent pandas call using `pandas.read_sql`? We need to get the connect_args passed through.
For `pandas.read_sql` I pass the connection object instead of Connection string. But as far as I have read in similar discussions it's quite complicated to pass the connection objects. @TomAugspurger 
Right, they're generally not serializable. So it seems like we may need to pass some arguments through to `_read_sql_chunk` to create the engine which will be passed to `pd.read_sql`?
I can investigate things further and try to find a workaround or a solution which is not complicated like serialisation of sqlalchemy objects. @TomAugspurger 
@TomAugspurger I will try sending a PR for this by tomorrow.

Resolving patch:
diff --git a/dask/dataframe/io/sql.py b/dask/dataframe/io/sql.py
--- a/dask/dataframe/io/sql.py
+++ b/dask/dataframe/io/sql.py
@@ -188,13 +188,21 @@ def read_sql_table(
     for i, (lower, upper) in enumerate(zip(lowers, uppers)):
         cond = index <= upper if i == len(lowers) - 1 else index < upper
         q = sql.select(columns).where(sql.and_(index >= lower, cond)).select_from(table)
-        parts.append(delayed(_read_sql_chunk)(q, uri, meta, **kwargs))
+        parts.append(
+            delayed(_read_sql_chunk)(
+                q, uri, meta, engine_kwargs=engine_kwargs, **kwargs
+            )
+        )
 
     return from_delayed(parts, meta, divisions=divisions)
 
 
-def _read_sql_chunk(q, uri, meta, **kwargs):
-    df = pd.read_sql(q, uri, **kwargs)
+def _read_sql_chunk(q, uri, meta, engine_kwargs=None, **kwargs):
+    import sqlalchemy as sa
+
+    engine_kwargs = engine_kwargs or {}
+    conn = sa.create_engine(uri, **engine_kwargs)
+    df = pd.read_sql(q, conn, **kwargs)
     if df.empty:
         return meta
     else:


Test patch:
diff --git a/dask/dataframe/io/tests/test_sql.py b/dask/dataframe/io/tests/test_sql.py
--- a/dask/dataframe/io/tests/test_sql.py
+++ b/dask/dataframe/io/tests/test_sql.py
@@ -320,7 +320,8 @@ def test_extra_connection_engine_keywords(capsys, db):
         "test", db, npartitions=2, index_col="number", engine_kwargs={"echo": True}
     ).compute()
     out, err = capsys.readouterr()
-    assert "SELECT" in out
+    assert "WHERE test.number >= ? AND test.number < ?" in out
+    assert "WHERE test.number >= ? AND test.number <= ?" in out
     assert_eq(data, df)