diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-08-09 13:21:13 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-08-09 13:21:13 -0400 |
commit | 10129773a7ca6a6cf1d10174e390adb9a33b784f (patch) | |
tree | 1052514600d384f7e83bf711f37a3be7b131a86b | |
parent | 481218e613642a9770fa0ad6af155105e7d4f0b9 (diff) | |
download | sqlalchemy-10129773a7ca6a6cf1d10174e390adb9a33b784f.tar.gz |
- begin moving general handling of __getitem__ and slice objects
into core expression. we need to make a sql.elements.Slice object,
which isn't a SQL standard thing; however there's no other place to
put this as long as we want slice handling to have some meaning
in Core. Basically any other backend that ever wants to use the []
operator, I don't want to re-implement everything for PG on that
backend as well. this will need to have backend-agnostic tests
in tests/sql/test_operator.py.
-rw-r--r-- | lib/sqlalchemy/dialects/postgresql/array.py | 33 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/default_comparator.py | 28 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/elements.py | 16 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/sqltypes.py | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/type_api.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/types.py | 4 |
6 files changed, 59 insertions, 35 deletions
diff --git a/lib/sqlalchemy/dialects/postgresql/array.py b/lib/sqlalchemy/dialects/postgresql/array.py index 900167933..5c8dad211 100644 --- a/lib/sqlalchemy/dialects/postgresql/array.py +++ b/lib/sqlalchemy/dialects/postgresql/array.py @@ -15,18 +15,6 @@ except ImportError: _python_UUID = None -class _Slice(expression.ColumnElement): - __visit_name__ = 'slice' - type = sqltypes.NULLTYPE - - def __init__(self, slice_, source_comparator): - self.start = default_comparator._check_literal( - source_comparator.expr, - operators.getitem, slice_.start) - self.stop = default_comparator._check_literal( - source_comparator.expr, - operators.getitem, slice_.stop) - class Any(expression.ColumnElement): @@ -121,7 +109,7 @@ class array(expression.Tuple): return self -class ARRAY(sqltypes.Concatenable, sqltypes.TypeEngine): +class ARRAY(sqltypes.Indexable, sqltypes.Concatenable, sqltypes.TypeEngine): """Postgresql ARRAY type. @@ -211,25 +199,6 @@ class ARRAY(sqltypes.Concatenable, sqltypes.TypeEngine): """Define comparison operations for :class:`.ARRAY`.""" - def __getitem__(self, index): - shift_indexes = 1 if self.expr.type.zero_indexes else 0 - if isinstance(index, slice): - if shift_indexes: - index = slice( - index.start + shift_indexes, - index.stop + shift_indexes, - index.step - ) - index = _Slice(index, self) - return_type = self.type - else: - index += shift_indexes - return_type = self.type.item_type - - return default_comparator._binary_operate( - self.expr, operators.getitem, index, - result_type=return_type) - def any(self, other, operator=operators.eq): """Return ``other operator ANY (array)`` clause. diff --git a/lib/sqlalchemy/sql/default_comparator.py b/lib/sqlalchemy/sql/default_comparator.py index e77ad765c..09f639163 100644 --- a/lib/sqlalchemy/sql/default_comparator.py +++ b/lib/sqlalchemy/sql/default_comparator.py @@ -14,7 +14,8 @@ from . import operators from .elements import BindParameter, True_, False_, BinaryExpression, \ Null, _const_expr, _clause_element_as_expr, \ ClauseList, ColumnElement, TextClause, UnaryExpression, \ - collate, _is_literal, _literal_as_text, ClauseElement, and_, or_ + collate, _is_literal, _literal_as_text, ClauseElement, and_, or_, \ + Slice from .selectable import SelectBase, Alias, Selectable, ScalarSelect @@ -161,6 +162,29 @@ def _in_impl(expr, op, seq_or_selectable, negate_op, **kw): negate=negate_op) +def _getitem_impl(expr, op, other, **kw): + if isinstance(expr.type, type_api.INDEXABLE): + if isinstance(other, slice): + if expr.type.zero_indexes: + other = slice( + other.start + 1, + other.stop + 1, + other.step + ) + other = Slice( + _check_literal(expr, op, other.start), + _check_literal(expr, op, other.stop), + _check_literal(expr, op, other.step), + ) + else: + if expr.type.zero_indexes: + other += 1 + + return _binary_operate(expr, op, other, **kw) + else: + _unsupported_impl(expr, op, other, **kw) + + def _unsupported_impl(expr, op, *arg, **kw): raise NotImplementedError("Operator '%s' is not supported on " "this expression" % op.__name__) @@ -260,7 +284,7 @@ operator_lookup = { "between_op": (_between_impl, ), "notbetween_op": (_between_impl, ), "neg": (_neg_impl,), - "getitem": (_unsupported_impl,), + "getitem": (_getitem_impl,), "lshift": (_unsupported_impl,), "rshift": (_unsupported_impl,), } diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 7a1330970..00c749b40 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -2799,6 +2799,22 @@ class BinaryExpression(ColumnElement): return super(BinaryExpression, self)._negate() +class Slice(ColumnElement): + """Represent SQL for a Python array-slice object. + + This is not a specific SQL construct at this level, but + may be interpreted by specific dialects, e.g. Postgresql. + + """ + __visit_name__ = 'slice' + + def __init__(self, start, stop, step): + self.start = start + self.stop = stop + self.step = step + self.type = type_api.NULLTYPE + + class IndexExpression(BinaryExpression): """Represent the class of expressions that are like an "index" operation. """ diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py index f7780b12d..fcc44fe74 100644 --- a/lib/sqlalchemy/sql/sqltypes.py +++ b/lib/sqlalchemy/sql/sqltypes.py @@ -73,6 +73,17 @@ class Concatenable(object): comparator_factory = Comparator +class Indexable(object): + """A mixin that marks a type as supporting indexing operations, + such as array or JSON structures. + + """ + + zero_indexes = False + """if True, Python zero-based indexes should be interpreted as one-based + on the SQL expression side.""" + + class String(Concatenable, TypeEngine): """The base for all string and character types. @@ -1714,6 +1725,7 @@ type_api.STRINGTYPE = STRINGTYPE type_api.INTEGERTYPE = INTEGERTYPE type_api.NULLTYPE = NULLTYPE type_api.MATCHTYPE = MATCHTYPE +type_api.INDEXABLE = Indexable type_api._type_map = _type_map TypeEngine.Comparator.BOOLEANTYPE = BOOLEANTYPE diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py index a55eed981..8f502ac02 100644 --- a/lib/sqlalchemy/sql/type_api.py +++ b/lib/sqlalchemy/sql/type_api.py @@ -20,6 +20,7 @@ INTEGERTYPE = None NULLTYPE = None STRINGTYPE = None MATCHTYPE = None +INDEXABLE = None class TypeEngine(Visitable): diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index 9ab92e90b..3a0e2a58f 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -16,7 +16,8 @@ __all__ = ['TypeEngine', 'TypeDecorator', 'UserDefinedType', 'SMALLINT', 'INTEGER', 'DATE', 'TIME', 'String', 'Integer', 'SmallInteger', 'BigInteger', 'Numeric', 'Float', 'DateTime', 'Date', 'Time', 'LargeBinary', 'Binary', 'Boolean', 'Unicode', - 'Concatenable', 'UnicodeText', 'PickleType', 'Interval', 'Enum'] + 'Concatenable', 'UnicodeText', 'PickleType', 'Interval', 'Enum', + 'Indexable'] from .sql.type_api import ( adapt_type, @@ -46,6 +47,7 @@ from .sql.sqltypes import ( Enum, FLOAT, Float, + Indexable, INT, INTEGER, Integer, |