# 🔍 Description This is the follow-up of #5686, renaming `./pyhive` to `./python`, and also adding `**/python/*` to RAT exclusion list temporarily. "PyHive" may not be a suitable name after being part of Apache Kyuubi, let's use a generic dir name `python`, and discuss the official name later(we probably keep the code at `./python` eventually). ## Types of changes 🔖 - [ ] Bugfix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) ## Test Plan 🧪 Recover RAT checked. --- # Checklist 📝 - [x] This patch was not authored or co-authored using [Generative Tooling](https://www.apache.org/legal/generative-tooling.html) **Be nice. Be informative.** Closes #6279 from pan3793/pyhive-1. Closes #5686 42d338e71 [Cheng Pan] [KYUUBI #5686][FOLLOWUP] Rename pyhive to python Authored-by: Cheng Pan <chengpan@apache.org> Signed-off-by: Cheng Pan <chengpan@apache.org>
267 lines
9.1 KiB
Python
267 lines
9.1 KiB
Python
"""Package private common utilities. Do not use directly.
|
|
|
|
Many docstrings in this file are based on PEP-249, which is in the public domain.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import unicode_literals
|
|
from builtins import bytes
|
|
from builtins import int
|
|
from builtins import object
|
|
from builtins import str
|
|
from past.builtins import basestring
|
|
from pyhive import exc
|
|
import abc
|
|
import collections
|
|
import time
|
|
import datetime
|
|
from future.utils import with_metaclass
|
|
from itertools import islice
|
|
|
|
try:
|
|
from collections.abc import Iterable
|
|
except ImportError:
|
|
from collections import Iterable
|
|
|
|
|
|
class DBAPICursor(with_metaclass(abc.ABCMeta, object)):
|
|
"""Base class for some common DB-API logic"""
|
|
|
|
_STATE_NONE = 0
|
|
_STATE_RUNNING = 1
|
|
_STATE_FINISHED = 2
|
|
|
|
def __init__(self, poll_interval=1):
|
|
self._poll_interval = poll_interval
|
|
self._reset_state()
|
|
self.lastrowid = None
|
|
|
|
def _reset_state(self):
|
|
"""Reset state about the previous query in preparation for running another query"""
|
|
# State to return as part of DB-API
|
|
self._rownumber = 0
|
|
|
|
# Internal helper state
|
|
self._state = self._STATE_NONE
|
|
self._data = collections.deque()
|
|
self._columns = None
|
|
|
|
def _fetch_while(self, fn):
|
|
while fn():
|
|
self._fetch_more()
|
|
if fn():
|
|
time.sleep(self._poll_interval)
|
|
|
|
@abc.abstractproperty
|
|
def description(self):
|
|
raise NotImplementedError # pragma: no cover
|
|
|
|
def close(self):
|
|
"""By default, do nothing"""
|
|
pass
|
|
|
|
@abc.abstractmethod
|
|
def _fetch_more(self):
|
|
"""Get more results, append it to ``self._data``, and update ``self._state``."""
|
|
raise NotImplementedError # pragma: no cover
|
|
|
|
@property
|
|
def rowcount(self):
|
|
"""By default, return -1 to indicate that this is not supported."""
|
|
return -1
|
|
|
|
@abc.abstractmethod
|
|
def execute(self, operation, parameters=None):
|
|
"""Prepare and execute a database operation (query or command).
|
|
|
|
Parameters may be provided as sequence or mapping and will be bound to variables in the
|
|
operation. Variables are specified in a database-specific notation (see the module's
|
|
``paramstyle`` attribute for details).
|
|
|
|
Return values are not defined.
|
|
"""
|
|
raise NotImplementedError # pragma: no cover
|
|
|
|
def executemany(self, operation, seq_of_parameters):
|
|
"""Prepare a database operation (query or command) and then execute it against all parameter
|
|
sequences or mappings found in the sequence ``seq_of_parameters``.
|
|
|
|
Only the final result set is retained.
|
|
|
|
Return values are not defined.
|
|
"""
|
|
for parameters in seq_of_parameters[:-1]:
|
|
self.execute(operation, parameters)
|
|
while self._state != self._STATE_FINISHED:
|
|
self._fetch_more()
|
|
if seq_of_parameters:
|
|
self.execute(operation, seq_of_parameters[-1])
|
|
|
|
def fetchone(self):
|
|
"""Fetch the next row of a query result set, returning a single sequence, or ``None`` when
|
|
no more data is available.
|
|
|
|
An :py:class:`~pyhive.exc.Error` (or subclass) exception is raised if the previous call to
|
|
:py:meth:`execute` did not produce any result set or no call was issued yet.
|
|
"""
|
|
if self._state == self._STATE_NONE:
|
|
raise exc.ProgrammingError("No query yet")
|
|
|
|
# Sleep until we're done or we have some data to return
|
|
self._fetch_while(lambda: not self._data and self._state != self._STATE_FINISHED)
|
|
|
|
if not self._data:
|
|
return None
|
|
else:
|
|
self._rownumber += 1
|
|
return self._data.popleft()
|
|
|
|
def fetchmany(self, size=None):
|
|
"""Fetch the next set of rows of a query result, returning a sequence of sequences (e.g. a
|
|
list of tuples). An empty sequence is returned when no more rows are available.
|
|
|
|
The number of rows to fetch per call is specified by the parameter. If it is not given, the
|
|
cursor's arraysize determines the number of rows to be fetched. The method should try to
|
|
fetch as many rows as indicated by the size parameter. If this is not possible due to the
|
|
specified number of rows not being available, fewer rows may be returned.
|
|
|
|
An :py:class:`~pyhive.exc.Error` (or subclass) exception is raised if the previous call to
|
|
:py:meth:`execute` did not produce any result set or no call was issued yet.
|
|
"""
|
|
if size is None:
|
|
size = self.arraysize
|
|
return list(islice(iter(self.fetchone, None), size))
|
|
|
|
def fetchall(self):
|
|
"""Fetch all (remaining) rows of a query result, returning them as a sequence of sequences
|
|
(e.g. a list of tuples).
|
|
|
|
An :py:class:`~pyhive.exc.Error` (or subclass) exception is raised if the previous call to
|
|
:py:meth:`execute` did not produce any result set or no call was issued yet.
|
|
"""
|
|
return list(iter(self.fetchone, None))
|
|
|
|
@property
|
|
def arraysize(self):
|
|
"""This read/write attribute specifies the number of rows to fetch at a time with
|
|
:py:meth:`fetchmany`. It defaults to 1 meaning to fetch a single row at a time.
|
|
"""
|
|
return self._arraysize
|
|
|
|
@arraysize.setter
|
|
def arraysize(self, value):
|
|
self._arraysize = value
|
|
|
|
def setinputsizes(self, sizes):
|
|
"""Does nothing by default"""
|
|
pass
|
|
|
|
def setoutputsize(self, size, column=None):
|
|
"""Does nothing by default"""
|
|
pass
|
|
|
|
#
|
|
# Optional DB API Extensions
|
|
#
|
|
|
|
@property
|
|
def rownumber(self):
|
|
"""This read-only attribute should provide the current 0-based index of the cursor in the
|
|
result set.
|
|
|
|
The index can be seen as index of the cursor in a sequence (the result set). The next fetch
|
|
operation will fetch the row indexed by ``rownumber`` in that sequence.
|
|
"""
|
|
return self._rownumber
|
|
|
|
def __next__(self):
|
|
"""Return the next row from the currently executing SQL statement using the same semantics
|
|
as :py:meth:`fetchone`. A ``StopIteration`` exception is raised when the result set is
|
|
exhausted.
|
|
"""
|
|
one = self.fetchone()
|
|
if one is None:
|
|
raise StopIteration
|
|
else:
|
|
return one
|
|
|
|
next = __next__
|
|
|
|
def __iter__(self):
|
|
"""Return self to make cursors compatible to the iteration protocol."""
|
|
return self
|
|
|
|
|
|
class DBAPITypeObject(object):
|
|
# Taken from http://www.python.org/dev/peps/pep-0249/#implementation-hints
|
|
def __init__(self, *values):
|
|
self.values = values
|
|
|
|
def __cmp__(self, other):
|
|
if other in self.values:
|
|
return 0
|
|
if other < self.values:
|
|
return 1
|
|
else:
|
|
return -1
|
|
|
|
|
|
class ParamEscaper(object):
|
|
_DATE_FORMAT = "%Y-%m-%d"
|
|
_TIME_FORMAT = "%H:%M:%S.%f"
|
|
_DATETIME_FORMAT = "{} {}".format(_DATE_FORMAT, _TIME_FORMAT)
|
|
|
|
def escape_args(self, parameters):
|
|
if isinstance(parameters, dict):
|
|
return {k: self.escape_item(v) for k, v in parameters.items()}
|
|
elif isinstance(parameters, (list, tuple)):
|
|
return tuple(self.escape_item(x) for x in parameters)
|
|
else:
|
|
raise exc.ProgrammingError("Unsupported param format: {}".format(parameters))
|
|
|
|
def escape_number(self, item):
|
|
return item
|
|
|
|
def escape_string(self, item):
|
|
# Need to decode UTF-8 because of old sqlalchemy.
|
|
# Newer SQLAlchemy checks dialect.supports_unicode_binds before encoding Unicode strings
|
|
# as byte strings. The old version always encodes Unicode as byte strings, which breaks
|
|
# string formatting here.
|
|
if isinstance(item, bytes):
|
|
item = item.decode('utf-8')
|
|
# This is good enough when backslashes are literal, newlines are just followed, and the way
|
|
# to escape a single quote is to put two single quotes.
|
|
# (i.e. only special character is single quote)
|
|
return "'{}'".format(item.replace("'", "''"))
|
|
|
|
def escape_sequence(self, item):
|
|
l = map(str, map(self.escape_item, item))
|
|
return '(' + ','.join(l) + ')'
|
|
|
|
def escape_datetime(self, item, format, cutoff=0):
|
|
dt_str = item.strftime(format)
|
|
formatted = dt_str[:-cutoff] if cutoff and format.endswith(".%f") else dt_str
|
|
return "'{}'".format(formatted)
|
|
|
|
def escape_item(self, item):
|
|
if item is None:
|
|
return 'NULL'
|
|
elif isinstance(item, (int, float)):
|
|
return self.escape_number(item)
|
|
elif isinstance(item, basestring):
|
|
return self.escape_string(item)
|
|
elif isinstance(item, Iterable):
|
|
return self.escape_sequence(item)
|
|
elif isinstance(item, datetime.datetime):
|
|
return self.escape_datetime(item, self._DATETIME_FORMAT)
|
|
elif isinstance(item, datetime.date):
|
|
return self.escape_datetime(item, self._DATE_FORMAT)
|
|
else:
|
|
raise exc.ProgrammingError("Unsupported object {}".format(item))
|
|
|
|
|
|
class UniversalSet(object):
|
|
"""set containing everything"""
|
|
def __contains__(self, item):
|
|
return True
|