summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-08-09 13:21:13 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2015-08-09 13:21:13 -0400
commit10129773a7ca6a6cf1d10174e390adb9a33b784f (patch)
tree1052514600d384f7e83bf711f37a3be7b131a86b
parent481218e613642a9770fa0ad6af155105e7d4f0b9 (diff)
downloadsqlalchemy-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.py33
-rw-r--r--lib/sqlalchemy/sql/default_comparator.py28
-rw-r--r--lib/sqlalchemy/sql/elements.py16
-rw-r--r--lib/sqlalchemy/sql/sqltypes.py12
-rw-r--r--lib/sqlalchemy/sql/type_api.py1
-rw-r--r--lib/sqlalchemy/types.py4
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,