| # Copyright DataStax, Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """ |
| These functions are used to convert Python objects into CQL strings. |
| When non-prepared statements are executed, these encoder functions are |
| called on each query parameter. |
| """ |
| |
| import logging |
| log = logging.getLogger(__name__) |
| |
| from binascii import hexlify |
| import calendar |
| import datetime |
| import math |
| import sys |
| import types |
| from uuid import UUID |
| import six |
| |
| from cassandra.util import (OrderedDict, OrderedMap, OrderedMapSerializedKey, |
| sortedset, Time, Date, Point, LineString, Polygon) |
| |
| if six.PY3: |
| import ipaddress |
| |
| if six.PY3: |
| long = int |
| |
| |
| def cql_quote(term): |
| # The ordering of this method is important for the result of this method to |
| # be a native str type (for both Python 2 and 3) |
| |
| if isinstance(term, str): |
| return "'%s'" % str(term).replace("'", "''") |
| # This branch of the if statement will only be used by Python 2 to catch |
| # unicode strings, text_type is used to prevent type errors with Python 3. |
| elif isinstance(term, six.text_type): |
| return "'%s'" % term.encode('utf8').replace("'", "''") |
| else: |
| return str(term) |
| |
| |
| class ValueSequence(list): |
| pass |
| |
| |
| class Encoder(object): |
| """ |
| A container for mapping python types to CQL string literals when working |
| with non-prepared statements. The type :attr:`~.Encoder.mapping` can be |
| directly customized by users. |
| """ |
| |
| mapping = None |
| """ |
| A map of python types to encoder functions. |
| """ |
| |
| def __init__(self): |
| self.mapping = { |
| float: self.cql_encode_float, |
| bytearray: self.cql_encode_bytes, |
| str: self.cql_encode_str, |
| int: self.cql_encode_object, |
| UUID: self.cql_encode_object, |
| datetime.datetime: self.cql_encode_datetime, |
| datetime.date: self.cql_encode_date, |
| datetime.time: self.cql_encode_time, |
| Date: self.cql_encode_date_ext, |
| Time: self.cql_encode_time, |
| dict: self.cql_encode_map_collection, |
| OrderedDict: self.cql_encode_map_collection, |
| OrderedMap: self.cql_encode_map_collection, |
| OrderedMapSerializedKey: self.cql_encode_map_collection, |
| list: self.cql_encode_list_collection, |
| tuple: self.cql_encode_list_collection, # TODO: change to tuple in next major |
| set: self.cql_encode_set_collection, |
| sortedset: self.cql_encode_set_collection, |
| frozenset: self.cql_encode_set_collection, |
| types.GeneratorType: self.cql_encode_list_collection, |
| ValueSequence: self.cql_encode_sequence, |
| Point: self.cql_encode_str_quoted, |
| LineString: self.cql_encode_str_quoted, |
| Polygon: self.cql_encode_str_quoted |
| } |
| |
| if six.PY2: |
| self.mapping.update({ |
| unicode: self.cql_encode_unicode, |
| buffer: self.cql_encode_bytes, |
| long: self.cql_encode_object, |
| types.NoneType: self.cql_encode_none, |
| }) |
| else: |
| self.mapping.update({ |
| memoryview: self.cql_encode_bytes, |
| bytes: self.cql_encode_bytes, |
| type(None): self.cql_encode_none, |
| ipaddress.IPv4Address: self.cql_encode_ipaddress, |
| ipaddress.IPv6Address: self.cql_encode_ipaddress |
| }) |
| |
| def cql_encode_none(self, val): |
| """ |
| Converts :const:`None` to the string 'NULL'. |
| """ |
| return 'NULL' |
| |
| def cql_encode_unicode(self, val): |
| """ |
| Converts :class:`unicode` objects to UTF-8 encoded strings with quote escaping. |
| """ |
| return cql_quote(val.encode('utf-8')) |
| |
| def cql_encode_str(self, val): |
| """ |
| Escapes quotes in :class:`str` objects. |
| """ |
| return cql_quote(val) |
| |
| def cql_encode_str_quoted(self, val): |
| return "'%s'" % val |
| |
| if six.PY3: |
| def cql_encode_bytes(self, val): |
| return (b'0x' + hexlify(val)).decode('utf-8') |
| elif sys.version_info >= (2, 7): |
| def cql_encode_bytes(self, val): # noqa |
| return b'0x' + hexlify(val) |
| else: |
| # python 2.6 requires string or read-only buffer for hexlify |
| def cql_encode_bytes(self, val): # noqa |
| return b'0x' + hexlify(buffer(val)) |
| |
| def cql_encode_object(self, val): |
| """ |
| Default encoder for all objects that do not have a specific encoder function |
| registered. This function simply calls :meth:`str()` on the object. |
| """ |
| return str(val) |
| |
| def cql_encode_float(self, val): |
| """ |
| Encode floats using repr to preserve precision |
| """ |
| if math.isinf(val): |
| return 'Infinity' if val > 0 else '-Infinity' |
| elif math.isnan(val): |
| return 'NaN' |
| else: |
| return repr(val) |
| |
| def cql_encode_datetime(self, val): |
| """ |
| Converts a :class:`datetime.datetime` object to a (string) integer timestamp |
| with millisecond precision. |
| """ |
| timestamp = calendar.timegm(val.utctimetuple()) |
| return str(long(timestamp * 1e3 + getattr(val, 'microsecond', 0) / 1e3)) |
| |
| def cql_encode_date(self, val): |
| """ |
| Converts a :class:`datetime.date` object to a string with format |
| ``YYYY-MM-DD``. |
| """ |
| return "'%s'" % val.strftime('%Y-%m-%d') |
| |
| def cql_encode_time(self, val): |
| """ |
| Converts a :class:`cassandra.util.Time` object to a string with format |
| ``HH:MM:SS.mmmuuunnn``. |
| """ |
| return "'%s'" % val |
| |
| def cql_encode_date_ext(self, val): |
| """ |
| Encodes a :class:`cassandra.util.Date` object as an integer |
| """ |
| # using the int form in case the Date exceeds datetime.[MIN|MAX]YEAR |
| return str(val.days_from_epoch + 2 ** 31) |
| |
| def cql_encode_sequence(self, val): |
| """ |
| Converts a sequence to a string of the form ``(item1, item2, ...)``. This |
| is suitable for ``IN`` value lists. |
| """ |
| return '(%s)' % ', '.join(self.mapping.get(type(v), self.cql_encode_object)(v) |
| for v in val) |
| |
| cql_encode_tuple = cql_encode_sequence |
| """ |
| Converts a sequence to a string of the form ``(item1, item2, ...)``. This |
| is suitable for ``tuple`` type columns. |
| """ |
| |
| def cql_encode_map_collection(self, val): |
| """ |
| Converts a dict into a string of the form ``{key1: val1, key2: val2, ...}``. |
| This is suitable for ``map`` type columns. |
| """ |
| return '{%s}' % ', '.join('%s: %s' % ( |
| self.mapping.get(type(k), self.cql_encode_object)(k), |
| self.mapping.get(type(v), self.cql_encode_object)(v) |
| ) for k, v in six.iteritems(val)) |
| |
| def cql_encode_list_collection(self, val): |
| """ |
| Converts a sequence to a string of the form ``[item1, item2, ...]``. This |
| is suitable for ``list`` type columns. |
| """ |
| return '[%s]' % ', '.join(self.mapping.get(type(v), self.cql_encode_object)(v) for v in val) |
| |
| def cql_encode_set_collection(self, val): |
| """ |
| Converts a sequence to a string of the form ``{item1, item2, ...}``. This |
| is suitable for ``set`` type columns. |
| """ |
| return '{%s}' % ', '.join(self.mapping.get(type(v), self.cql_encode_object)(v) for v in val) |
| |
| def cql_encode_all_types(self, val, as_text_type=False): |
| """ |
| Converts any type into a CQL string, defaulting to ``cql_encode_object`` |
| if :attr:`~Encoder.mapping` does not contain an entry for the type. |
| """ |
| encoded = self.mapping.get(type(val), self.cql_encode_object)(val) |
| if as_text_type and not isinstance(encoded, six.text_type): |
| return encoded.decode('utf-8') |
| return encoded |
| |
| if six.PY3: |
| def cql_encode_ipaddress(self, val): |
| """ |
| Converts an ipaddress (IPV4Address, IPV6Address) to a CQL string. This |
| is suitable for ``inet`` type columns. |
| """ |
| return "'%s'" % val.compressed |