Skip to content

Commit 5bfea3b

Browse files
authored
Add an impersonation_target parm to drill+sadrill URLs. (#74)
When present, this parameter will be converted to a userName property in POSTs made to /query.json.
1 parent 2d60846 commit 5bfea3b

File tree

6 files changed

+70
-22
lines changed

6 files changed

+70
-22
lines changed

.editorconfig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 2
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true
10+
11+
[*.py]
12+
indent_size = 4
13+
max_line_length = 80
14+

CHANGELOG.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
## [Unreleased]
22

3+
## [1.1.2] - 2022-03-14
4+
5+
### Changed
6+
7+
- Add an impersonation_target parm to drill+sadrill URLs. When present,
8+
this parameter will be converted to a userName property in POSTs made to
9+
/query.json.
10+
311
## [1.1.1] - 2021-07-28
412

513
### Fixed
614

7-
- Backwards compatibility with Drill < 1.19, limited to returning
8-
all data values as strings. Users not able to upgrade to >= 1.19
9-
must implement their own typecasting or use sqlalchemy-drill 0.3.
15+
- Backwards compatibility with Drill < 1.19, limited to returning all data
16+
values as strings. Users not able to upgrade to >= 1.19 must implement their
17+
own typecasting or use sqlalchemy-drill 0.3.
1018

1119
## [1.1.0] - 2021-07-21
1220

README.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ Alternatively, you can download the latest release from github and install from
2222
python3 -m pip install git+https://github.com/JohnOmernik/sqlalchemy-drill.git
2323
```
2424

25-
## Usage
25+
## Usage with REST
2626

27-
To use Drill with SQLAlchemy you will need to craft a connection string in the format below:
27+
Drill's REST API can execute queries with results streamed to JSON returned over chunked HTTP for Drill >= 1.19, otherwise with results buffered and then returned in a conventional HTTP response. A SQLAlchemy URL to connect to Drill over REST looks like the following.
2828

2929
```
3030
drill+sadrill://<username>:<password>@<host>:<port>/<storage_plugin>?use_ssl=True
@@ -36,7 +36,20 @@ To connect to Drill running on a local machine running in embedded mode you can
3636
drill+sadrill://localhost:8047/dfs?use_ssl=False
3737
```
3838

39+
### Supported URL query parameters
40+
41+
| Parameter | Type | Description |
42+
| ------------------------- | ------- | -------------------------------------------------------------- |
43+
| use_ssl | boolean | Whether to connect to Drill using HTTPS |
44+
| verify_ssl | boolean | Whether to verify the server's TLS certificate |
45+
| impersonation_target\[1\] | string | Username of a Drill user to be impersonated by this connection |
46+
47+
[1] Requires a build of Drill that incorporates the fix for DRILL-8168.
48+
49+
### Trailing metadata
50+
3951
Query result metadata returned by the Drill REST API is stored in the `result_md` field of the DB-API Cursor object. Note that any trailing metadata, i.e. metadata which comes after result row data, will only be populated after you have iterated through all of the returned rows. If you need this trailing metadata you can make the cursor object reachable after it has been completely iterated by obtaining a reference to it beforehand, as follows.
52+
4053
```python
4154
r = engine.execute('select current_timestamp')
4255
r.cursor.result_md # access metadata, but only leading metadata
@@ -46,11 +59,13 @@ cur.result_md # access metadata, including trailing metadata
4659
del cur # optionally delete the reference when done
4760
```
4861

49-
### Changes in Drill 1.19 affecting drill+sadrill
62+
### Drill < 1.19
5063

5164
In versions of Drill earlier than 1.19, all data values are serialised to JSON strings and column type metadata comes after the data itself. As a result, for these versions of Drill, the drill+sadrill dialect returns every data value as a string. To convert non-string data to its native type you need to typecast it yourself.
5265

53-
In Drill 1.19 the REST API began making use of numeric types in JSON for numbers and times, the latter via a UNIX time representation. As a result, the drill+sadrill dialect is able to return appropriate types for numbers and times when used with Drill >= 1.19.
66+
### Drill >= 1.19
67+
68+
In Drill 1.19 the REST API began making use of numeric types in JSON for numbers and times, the latter via a UNIX time representation while column type metadata was moved ahead of the result data. Because of this, the drill+sadrill dialect is able to return appropriate types for numbers and times when used with Drill >= 1.19.
5469

5570
## Usage with JDBC
5671

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
long_description = f.read()
3030

3131
setup(name='sqlalchemy_drill',
32-
version='1.1.1',
32+
version='1.1.2',
3333
description="Apache Drill for SQLAlchemy",
3434
long_description=long_description,
3535
long_description_content_type="text/markdown",
@@ -64,7 +64,7 @@
6464
license='MIT',
6565
url='https://github.com/JohnOmernik/sqlalchemy-drill',
6666
download_url='https://github.com/JohnOmernik/sqlalchemy-drill/archive/'
67-
'1.1.1.tar.gz',
67+
'1.1.2.tar.gz',
6868
packages=find_packages(),
6969
include_package_data=True,
7070
tests_require=['nose >= 0.11'],

sqlalchemy_drill/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
2020
# DEALINGS IN THE SOFTWARE.
2121

22-
__version__ = '1.1.1'
22+
__version__ = '1.1.2'
2323
from sqlalchemy.dialects import registry
2424

2525
registry.register("drill", "sqlalchemy_drill.sadrill", "DrillDialect_sadrill")

sqlalchemy_drill/drilldbapi/_drilldbapi.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -331,13 +331,15 @@ def __init__(self,
331331
host: str,
332332
port: int,
333333
proto: str,
334+
impersonation_target: str,
334335
session: Session):
335336
if session is None:
336337
raise ProgrammingError('A Requests session is required.', None)
337338

338339
self._base_url = f'{proto}{host}:{port}'
339340
self._session = session
340341
self._connected = True
342+
self._impersonation_target = impersonation_target
341343

342344
logger.debug('queries Drill\'s version number...')
343345
resp = self.submit_query(
@@ -361,22 +363,31 @@ def __init__(self,
361363

362364
def submit_query(self, query: str):
363365
payload = api_globals._PAYLOAD.copy()
366+
payload['userName'] = self._impersonation_target
367+
364368
# TODO: autoLimit, defaultSchema
365369
payload['query'] = query
366370

367371
logger.debug('sends an HTTP POST with payload')
368372
logger.debug(payload)
369373

370-
return self._session.post(
374+
resp = self._session.post(
371375
f'{self._base_url}/query.json',
372376
data=dumps(payload),
373377
headers=api_globals._HEADER,
374378
timeout=None,
375379
stream=True
376380
)
377381

378-
# Decorator for methods which require connection
382+
if resp.status_code == 200:
383+
return resp
384+
else:
385+
raise DatabaseError(
386+
resp.json().get('errorMessage', None),
387+
resp.status_code
388+
)
379389

390+
# Decorator for methods which require connection
380391
def connected(func):
381392

382393
def func_wrapper(self, *args, **kwargs):
@@ -419,24 +430,24 @@ def connect(host: str,
419430
drilluser: str = None,
420431
drillpass: str = None,
421432
verify_ssl: bool = False,
422-
ca_certs: bool = None,
433+
impersonation_target: str = None
423434
) -> Connection:
424435

425436
session = Session()
426437

427-
if verify_ssl is False:
428-
session.verify = False
429-
else:
430-
if ca_certs is not None:
431-
session.verify = ca_certs
432-
else:
433-
session.verify = True
434-
438+
session.verify = verify_ssl
435439
proto = 'https://' if use_ssl in [True, 'True', 'true'] else 'http://'
436440
base_url = f'{proto}{host}:{port}'
437441

442+
logging.info(
443+
f'will log in with user {drilluser} and impersonation target '
444+
f'{impersonation_target}'
445+
)
446+
438447
if drilluser is None:
439448
payload = api_globals._PAYLOAD.copy()
449+
payload['userName'] = impersonation_target
450+
440451
payload['query'] = 'show schemas'
441452
response = session.post(
442453
f'{base_url}/query.json',
@@ -464,7 +475,7 @@ def connect(host: str,
464475
logger.error('failed to authenticate to Drill.')
465476
raise AuthError(str(raw_data), response.status_code)
466477

467-
conn = Connection(host, port, proto, session)
478+
conn = Connection(host, port, proto, impersonation_target, session)
468479
if db is not None:
469480
conn.submit_query(f'USE {db}')
470481

0 commit comments

Comments
 (0)