mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-16 05:37:16 -04:00
ad11c482e2
after some experimentation it seems mypy is more amenable to the generic types being fully integrated rather than having separate spin-off types. so key structures like Result, Row, Select become generic. For DML Insert, Update, Delete, these are spun into type-specific subclasses ReturningInsert, ReturningUpdate, ReturningDelete, which is fine since the "row-ness" of these constructs doesn't happen until returning() is called in any case. a Tuple based model is then integrated so that these objects can carry along information about their return types. Overloads at the .execute() level carry through the Tuple from the invoked object to the result. To suit the issue of AliasedClass generating attributes that are dynamic, experimented with a custom subclass AsAliased, but then just settled on having aliased() lie to the type checker and return `Type[_O]`, essentially. will need some type-related accessors for with_polymorphic() also. Additionally, identified an issue in Update when used "mysql style" against a join(), it basically doesn't work if asked to UPDATE two tables on the same column name. added an error message to the specific condition where it happens with a very non-specific error message that we hit a thing we can't do right now, suggest multi-table update as a possible cause. Change-Id: I5eff7eefe1d6166ee74160b2785c5e6a81fa8b95
1297 lines
35 KiB
Python
1297 lines
35 KiB
Python
from sqlalchemy import exc
|
|
from sqlalchemy import testing
|
|
from sqlalchemy.engine import result
|
|
from sqlalchemy.testing import assert_raises
|
|
from sqlalchemy.testing import assert_raises_message
|
|
from sqlalchemy.testing import eq_
|
|
from sqlalchemy.testing import fixtures
|
|
from sqlalchemy.testing import is_false
|
|
from sqlalchemy.testing import is_true
|
|
from sqlalchemy.testing.util import picklers
|
|
|
|
|
|
class ResultTupleTest(fixtures.TestBase):
|
|
def _fixture(self, values, labels):
|
|
return result.result_tuple(labels)(values)
|
|
|
|
def test_empty(self):
|
|
keyed_tuple = self._fixture([], [])
|
|
eq_(str(keyed_tuple), "()")
|
|
eq_(len(keyed_tuple), 0)
|
|
|
|
eq_(list(keyed_tuple._mapping.keys()), [])
|
|
eq_(keyed_tuple._fields, ())
|
|
eq_(keyed_tuple._asdict(), {})
|
|
|
|
def test_values_none_labels(self):
|
|
keyed_tuple = self._fixture([1, 2], [None, None])
|
|
eq_(str(keyed_tuple), "(1, 2)")
|
|
eq_(len(keyed_tuple), 2)
|
|
|
|
eq_(list(keyed_tuple._mapping.keys()), [])
|
|
eq_(keyed_tuple._fields, ())
|
|
eq_(keyed_tuple._asdict(), {})
|
|
|
|
eq_(keyed_tuple[0], 1)
|
|
eq_(keyed_tuple[1], 2)
|
|
|
|
def test_creation(self):
|
|
keyed_tuple = self._fixture([1, 2], ["a", "b"])
|
|
eq_(str(keyed_tuple), "(1, 2)")
|
|
eq_(list(keyed_tuple._mapping.keys()), ["a", "b"])
|
|
eq_(keyed_tuple._fields, ("a", "b"))
|
|
eq_(keyed_tuple._asdict(), {"a": 1, "b": 2})
|
|
|
|
def test_index_access(self):
|
|
keyed_tuple = self._fixture([1, 2], ["a", "b"])
|
|
eq_(keyed_tuple[0], 1)
|
|
eq_(keyed_tuple[1], 2)
|
|
|
|
def should_raise():
|
|
keyed_tuple[2]
|
|
|
|
assert_raises(IndexError, should_raise)
|
|
|
|
def test_negative_index_access(self):
|
|
keyed_tuple = self._fixture([1, 2], ["a", "b"])
|
|
eq_(keyed_tuple[-1], 2)
|
|
eq_(keyed_tuple[-2:-1], (1,))
|
|
|
|
def test_slice_access(self):
|
|
keyed_tuple = self._fixture([1, 2], ["a", "b"])
|
|
eq_(keyed_tuple[0:2], (1, 2))
|
|
|
|
def test_slices_arent_in_mappings(self):
|
|
keyed_tuple = self._fixture([1, 2], ["a", "b"])
|
|
|
|
assert_raises(TypeError, lambda: keyed_tuple._mapping[0:2])
|
|
|
|
def test_integers_arent_in_mappings(self):
|
|
keyed_tuple = self._fixture([1, 2], ["a", "b"])
|
|
|
|
assert_raises(KeyError, lambda: keyed_tuple._mapping[1])
|
|
|
|
def test_getter(self):
|
|
keyed_tuple = self._fixture([1, 2, 3], ["a", "b", "c"])
|
|
|
|
getter = keyed_tuple._parent._getter("b")
|
|
eq_(getter(keyed_tuple), 2)
|
|
|
|
getter = keyed_tuple._parent._getter(2)
|
|
eq_(getter(keyed_tuple), 3)
|
|
|
|
def test_tuple_getter(self):
|
|
keyed_tuple = self._fixture([1, 2, 3], ["a", "b", "c"])
|
|
|
|
getter = keyed_tuple._parent._row_as_tuple_getter(["b", "c"])
|
|
eq_(getter(keyed_tuple), (2, 3))
|
|
|
|
# row as tuple getter doesn't accept ints. for ints, just
|
|
# use plain python
|
|
import operator
|
|
|
|
getter = operator.itemgetter(2, 0, 1)
|
|
|
|
# getter = keyed_tuple._parent._row_as_tuple_getter([2, 0, 1])
|
|
eq_(getter(keyed_tuple), (3, 1, 2))
|
|
|
|
def test_attribute_access(self):
|
|
keyed_tuple = self._fixture([1, 2], ["a", "b"])
|
|
eq_(keyed_tuple.a, 1)
|
|
eq_(keyed_tuple.b, 2)
|
|
|
|
def should_raise():
|
|
keyed_tuple.c
|
|
|
|
assert_raises(AttributeError, should_raise)
|
|
|
|
def test_contains(self):
|
|
keyed_tuple = self._fixture(["x", "y"], ["a", "b"])
|
|
|
|
is_true("x" in keyed_tuple)
|
|
is_false("z" in keyed_tuple)
|
|
|
|
is_true("z" not in keyed_tuple)
|
|
is_false("x" not in keyed_tuple)
|
|
|
|
# we don't do keys
|
|
is_false("a" in keyed_tuple)
|
|
is_false("z" in keyed_tuple)
|
|
is_true("a" not in keyed_tuple)
|
|
is_true("z" not in keyed_tuple)
|
|
|
|
def test_contains_mapping(self):
|
|
keyed_tuple = self._fixture(["x", "y"], ["a", "b"])._mapping
|
|
|
|
is_false("x" in keyed_tuple)
|
|
is_false("z" in keyed_tuple)
|
|
|
|
is_true("z" not in keyed_tuple)
|
|
is_true("x" not in keyed_tuple)
|
|
|
|
# we do keys
|
|
is_true("a" in keyed_tuple)
|
|
is_true("b" in keyed_tuple)
|
|
|
|
def test_none_label(self):
|
|
keyed_tuple = self._fixture([1, 2, 3], ["a", None, "b"])
|
|
eq_(str(keyed_tuple), "(1, 2, 3)")
|
|
|
|
eq_(list(keyed_tuple._mapping.keys()), ["a", "b"])
|
|
eq_(keyed_tuple._fields, ("a", "b"))
|
|
eq_(keyed_tuple._asdict(), {"a": 1, "b": 3})
|
|
|
|
# attribute access: can't get at value 2
|
|
eq_(keyed_tuple.a, 1)
|
|
eq_(keyed_tuple.b, 3)
|
|
|
|
# index access: can get at value 2
|
|
eq_(keyed_tuple[0], 1)
|
|
eq_(keyed_tuple[1], 2)
|
|
eq_(keyed_tuple[2], 3)
|
|
|
|
def test_duplicate_labels(self):
|
|
keyed_tuple = self._fixture([1, 2, 3], ["a", "b", "b"])
|
|
eq_(str(keyed_tuple), "(1, 2, 3)")
|
|
|
|
eq_(list(keyed_tuple._mapping.keys()), ["a", "b", "b"])
|
|
eq_(keyed_tuple._fields, ("a", "b", "b"))
|
|
eq_(keyed_tuple._asdict(), {"a": 1, "b": 3})
|
|
|
|
# attribute access: can't get at value 2
|
|
eq_(keyed_tuple.a, 1)
|
|
eq_(keyed_tuple.b, 3)
|
|
|
|
# index access: can get at value 2
|
|
eq_(keyed_tuple[0], 1)
|
|
eq_(keyed_tuple[1], 2)
|
|
eq_(keyed_tuple[2], 3)
|
|
|
|
def test_immutable(self):
|
|
keyed_tuple = self._fixture([1, 2], ["a", "b"])
|
|
eq_(str(keyed_tuple), "(1, 2)")
|
|
|
|
eq_(keyed_tuple.a, 1)
|
|
|
|
# eh
|
|
# assert_raises(AttributeError, setattr, keyed_tuple, "a", 5)
|
|
|
|
def should_raise():
|
|
keyed_tuple[0] = 100
|
|
|
|
assert_raises(TypeError, should_raise)
|
|
|
|
def test_serialize(self):
|
|
|
|
keyed_tuple = self._fixture([1, 2, 3], ["a", None, "b"])
|
|
|
|
for loads, dumps in picklers():
|
|
kt = loads(dumps(keyed_tuple))
|
|
|
|
eq_(str(kt), "(1, 2, 3)")
|
|
|
|
eq_(list(kt._mapping.keys()), ["a", "b"])
|
|
eq_(kt._fields, ("a", "b"))
|
|
eq_(kt._asdict(), {"a": 1, "b": 3})
|
|
|
|
@testing.requires.cextensions
|
|
def test_serialize_cy_py_cy(self):
|
|
from sqlalchemy.engine._py_row import BaseRow as _PyRow
|
|
from sqlalchemy.cyextension.resultproxy import BaseRow as _CyRow
|
|
|
|
global Row
|
|
|
|
p = result.SimpleResultMetaData(["a", None, "b"])
|
|
|
|
for loads, dumps in picklers():
|
|
|
|
class Row(_CyRow):
|
|
pass
|
|
|
|
row = Row(p, p._processors, p._keymap, 0, (1, 2, 3))
|
|
|
|
state = dumps(row)
|
|
|
|
class Row(_PyRow):
|
|
pass
|
|
|
|
row2 = loads(state)
|
|
is_true(isinstance(row2, _PyRow))
|
|
state2 = dumps(row2)
|
|
|
|
class Row(_CyRow):
|
|
pass
|
|
|
|
row3 = loads(state2)
|
|
is_true(isinstance(row3, _CyRow))
|
|
|
|
|
|
class ResultTest(fixtures.TestBase):
|
|
def _fixture(
|
|
self,
|
|
extras=None,
|
|
alt_row=None,
|
|
num_rows=None,
|
|
default_filters=None,
|
|
data=None,
|
|
):
|
|
|
|
if data is None:
|
|
data = [(1, 1, 1), (2, 1, 2), (1, 3, 2), (4, 1, 2)]
|
|
if num_rows is not None:
|
|
data = data[:num_rows]
|
|
|
|
res = result.IteratorResult(
|
|
result.SimpleResultMetaData(["a", "b", "c"], extra=extras),
|
|
iter(data),
|
|
)
|
|
if default_filters:
|
|
res._metadata._unique_filters = default_filters
|
|
|
|
if alt_row:
|
|
res._process_row = alt_row
|
|
|
|
return res
|
|
|
|
def test_class_presented(self):
|
|
"""To support different kinds of objects returned vs. rows,
|
|
there are two wrapper classes for Result.
|
|
"""
|
|
|
|
r1 = self._fixture()
|
|
|
|
r2 = r1.columns(0, 1, 2)
|
|
assert isinstance(r2, result.Result)
|
|
|
|
m1 = r1.mappings()
|
|
assert isinstance(m1, result.MappingResult)
|
|
|
|
s1 = r1.scalars(1)
|
|
assert isinstance(s1, result.ScalarResult)
|
|
|
|
def test_mapping_plus_base(self):
|
|
r1 = self._fixture()
|
|
|
|
m1 = r1.mappings()
|
|
eq_(m1.fetchone(), {"a": 1, "b": 1, "c": 1})
|
|
eq_(r1.fetchone(), (2, 1, 2))
|
|
|
|
def test_tuples_plus_base(self):
|
|
r1 = self._fixture()
|
|
|
|
t1 = r1.tuples()
|
|
eq_(t1.fetchone(), (1, 1, 1))
|
|
eq_(r1.fetchone(), (2, 1, 2))
|
|
|
|
def test_scalar_plus_base(self):
|
|
r1 = self._fixture()
|
|
|
|
m1 = r1.scalars()
|
|
|
|
# base is not affected
|
|
eq_(r1.fetchone(), (1, 1, 1))
|
|
|
|
# scalars
|
|
eq_(m1.first(), 2)
|
|
|
|
def test_index_extra(self):
|
|
ex1a, ex1b, ex2, ex3a, ex3b = (
|
|
object(),
|
|
object(),
|
|
object(),
|
|
object(),
|
|
object(),
|
|
)
|
|
|
|
result = self._fixture(
|
|
extras=[
|
|
(ex1a, ex1b),
|
|
(ex2,),
|
|
(
|
|
ex3a,
|
|
ex3b,
|
|
),
|
|
]
|
|
)
|
|
eq_(
|
|
result.columns(ex2, ex3b).columns(ex3a).all(),
|
|
[(1,), (2,), (2,), (2,)],
|
|
)
|
|
|
|
result = self._fixture(
|
|
extras=[
|
|
(ex1a, ex1b),
|
|
(ex2,),
|
|
(
|
|
ex3a,
|
|
ex3b,
|
|
),
|
|
]
|
|
)
|
|
eq_([row._mapping[ex1b] for row in result], [1, 2, 1, 4])
|
|
|
|
result = self._fixture(
|
|
extras=[
|
|
(ex1a, ex1b),
|
|
(ex2,),
|
|
(
|
|
ex3a,
|
|
ex3b,
|
|
),
|
|
]
|
|
)
|
|
eq_(
|
|
[
|
|
dict(r)
|
|
for r in result.columns(ex2, ex3b).columns(ex3a).mappings()
|
|
],
|
|
[{"c": 1}, {"c": 2}, {"c": 2}, {"c": 2}],
|
|
)
|
|
|
|
def test_unique_default_filters(self):
|
|
result = self._fixture(
|
|
default_filters=[lambda x: x < 4, lambda x: x, lambda x: True]
|
|
)
|
|
|
|
eq_(result.unique().all(), [(1, 1, 1), (1, 3, 2), (4, 1, 2)])
|
|
|
|
def test_unique_default_filters_rearrange_scalar(self):
|
|
result = self._fixture(
|
|
default_filters=[lambda x: x < 4, lambda x: x, lambda x: True]
|
|
)
|
|
|
|
eq_(result.unique().scalars(1).all(), [1, 3])
|
|
|
|
def test_unique_default_filters_rearrange_order(self):
|
|
result = self._fixture(
|
|
default_filters=[lambda x: x < 4, lambda x: x, lambda x: True]
|
|
)
|
|
|
|
eq_(
|
|
result.unique().columns("b", "a", "c").all(),
|
|
[(1, 1, 1), (3, 1, 2), (1, 4, 2)],
|
|
)
|
|
|
|
def test_unique_default_filters_rearrange_twice(self):
|
|
# test that the default uniqueness filter is reconfigured
|
|
# each time columns() is called
|
|
result = self._fixture(
|
|
default_filters=[lambda x: x < 4, lambda x: x, lambda x: True]
|
|
)
|
|
|
|
result = result.unique()
|
|
|
|
# 1, 1, 1 -> True, 1, True
|
|
eq_(result.fetchone(), (1, 1, 1))
|
|
|
|
# now rearrange for b, a, c
|
|
# 1, 2, 2 -> 1, True, True
|
|
# 3, 1, 2 -> 3, True, True
|
|
result = result.columns("b", "a", "c")
|
|
eq_(result.fetchone(), (3, 1, 2))
|
|
|
|
# now rearrange for c, a
|
|
# 2, 4 -> True, False
|
|
result = result.columns("c", "a")
|
|
eq_(result.fetchall(), [(2, 4)])
|
|
|
|
def test_unique_scalars_all(self):
|
|
result = self._fixture()
|
|
|
|
eq_(result.unique().scalars(1).all(), [1, 3])
|
|
|
|
def test_unique_mappings_all(self):
|
|
result = self._fixture()
|
|
|
|
def uniq(row):
|
|
return row[0]
|
|
|
|
eq_(
|
|
result.unique(uniq).mappings().all(),
|
|
[
|
|
{"a": 1, "b": 1, "c": 1},
|
|
{"a": 2, "b": 1, "c": 2},
|
|
{"a": 4, "b": 1, "c": 2},
|
|
],
|
|
)
|
|
|
|
def test_unique_filtered_all(self):
|
|
result = self._fixture()
|
|
|
|
def uniq(row):
|
|
return row[0]
|
|
|
|
eq_(result.unique(uniq).all(), [(1, 1, 1), (2, 1, 2), (4, 1, 2)])
|
|
|
|
def test_unique_scalars_many(self):
|
|
result = self._fixture()
|
|
|
|
result = result.unique().scalars(1)
|
|
|
|
eq_(result.fetchmany(2), [1, 3])
|
|
eq_(result.fetchmany(2), [])
|
|
|
|
def test_unique_filtered_many(self):
|
|
result = self._fixture()
|
|
|
|
def uniq(row):
|
|
return row[0]
|
|
|
|
result = result.unique(uniq)
|
|
|
|
eq_(result.fetchmany(2), [(1, 1, 1), (2, 1, 2)])
|
|
eq_(result.fetchmany(2), [(4, 1, 2)])
|
|
eq_(result.fetchmany(2), [])
|
|
|
|
def test_unique_scalars_many_none(self):
|
|
result = self._fixture()
|
|
|
|
result = result.unique().scalars(1)
|
|
|
|
# this assumes the default fetchmany behavior of all() for
|
|
# the ListFetchStrategy
|
|
eq_(result.fetchmany(None), [1, 3])
|
|
eq_(result.fetchmany(None), [])
|
|
|
|
def test_unique_scalars_iterate(self):
|
|
result = self._fixture()
|
|
|
|
result = result.unique().scalars(1)
|
|
|
|
eq_(list(result), [1, 3])
|
|
|
|
def test_unique_filtered_iterate(self):
|
|
result = self._fixture()
|
|
|
|
def uniq(row):
|
|
return row[0]
|
|
|
|
result = result.unique(uniq)
|
|
|
|
eq_(list(result), [(1, 1, 1), (2, 1, 2), (4, 1, 2)])
|
|
|
|
def test_all(self):
|
|
result = self._fixture()
|
|
|
|
eq_(result.all(), [(1, 1, 1), (2, 1, 2), (1, 3, 2), (4, 1, 2)])
|
|
|
|
eq_(result.all(), [])
|
|
|
|
def test_many_then_all(self):
|
|
result = self._fixture()
|
|
|
|
eq_(result.fetchmany(3), [(1, 1, 1), (2, 1, 2), (1, 3, 2)])
|
|
eq_(result.all(), [(4, 1, 2)])
|
|
|
|
eq_(result.all(), [])
|
|
|
|
def test_scalars(self):
|
|
result = self._fixture()
|
|
|
|
eq_(list(result.scalars()), [1, 2, 1, 4])
|
|
|
|
result = self._fixture()
|
|
|
|
eq_(list(result.scalars(2)), [1, 2, 2, 2])
|
|
|
|
def test_scalars_mappings(self):
|
|
result = self._fixture()
|
|
|
|
eq_(
|
|
list(result.columns(0).mappings()),
|
|
[{"a": 1}, {"a": 2}, {"a": 1}, {"a": 4}],
|
|
)
|
|
|
|
def test_scalars_no_fetchone(self):
|
|
result = self._fixture()
|
|
|
|
s = result.scalars()
|
|
|
|
assert not hasattr(s, "fetchone")
|
|
|
|
# original result is unchanged
|
|
eq_(result.mappings().fetchone(), {"a": 1, "b": 1, "c": 1})
|
|
|
|
# scalars
|
|
eq_(s.all(), [2, 1, 4])
|
|
|
|
def test_first(self):
|
|
result = self._fixture()
|
|
|
|
row = result.first()
|
|
eq_(row, (1, 1, 1))
|
|
|
|
# note this is a behavior change in 1.4.27 due to
|
|
# adding a real result.close() to Result, previously this would
|
|
# return an empty list. this is already the
|
|
# behavior with CursorResult, but was mis-implemented for
|
|
# other non-cursor result sets.
|
|
assert_raises(exc.ResourceClosedError, result.all)
|
|
|
|
def test_one_unique(self):
|
|
# assert that one() counts rows after uniqueness has been applied.
|
|
# this would raise if we didn't have unique
|
|
result = self._fixture(data=[(1, 1, 1), (1, 1, 1)])
|
|
|
|
row = result.unique().one()
|
|
eq_(row, (1, 1, 1))
|
|
|
|
def test_one_unique_tricky_one(self):
|
|
# one() needs to keep consuming rows in order to find a non-unique
|
|
# one. unique() really slows things down
|
|
result = self._fixture(
|
|
data=[(1, 1, 1), (1, 1, 1), (1, 1, 1), (2, 1, 1)]
|
|
)
|
|
|
|
assert_raises(exc.MultipleResultsFound, result.unique().one)
|
|
|
|
def test_one_unique_mapping(self):
|
|
# assert that one() counts rows after uniqueness has been applied.
|
|
# this would raise if we didn't have unique
|
|
result = self._fixture(data=[(1, 1, 1), (1, 1, 1)])
|
|
|
|
row = result.mappings().unique().one()
|
|
eq_(row, {"a": 1, "b": 1, "c": 1})
|
|
|
|
def test_one_mapping(self):
|
|
result = self._fixture(num_rows=1)
|
|
|
|
row = result.mappings().one()
|
|
eq_(row, {"a": 1, "b": 1, "c": 1})
|
|
|
|
def test_one(self):
|
|
result = self._fixture(num_rows=1)
|
|
|
|
row = result.one()
|
|
eq_(row, (1, 1, 1))
|
|
|
|
def test_scalar_one(self):
|
|
result = self._fixture(num_rows=1)
|
|
|
|
row = result.scalar_one()
|
|
eq_(row, 1)
|
|
|
|
def test_scalars_plus_one(self):
|
|
result = self._fixture(num_rows=1)
|
|
|
|
row = result.scalars().one()
|
|
eq_(row, 1)
|
|
|
|
def test_scalars_plus_one_none(self):
|
|
result = self._fixture(num_rows=0)
|
|
|
|
result = result.scalars()
|
|
assert_raises_message(
|
|
exc.NoResultFound,
|
|
"No row was found when one was required",
|
|
result.one,
|
|
)
|
|
|
|
def test_one_none(self):
|
|
result = self._fixture(num_rows=0)
|
|
|
|
assert_raises_message(
|
|
exc.NoResultFound,
|
|
"No row was found when one was required",
|
|
result.one,
|
|
)
|
|
|
|
def test_one_or_none(self):
|
|
result = self._fixture(num_rows=1)
|
|
|
|
eq_(result.one_or_none(), (1, 1, 1))
|
|
|
|
def test_scalar_one_or_none(self):
|
|
result = self._fixture(num_rows=1)
|
|
|
|
eq_(result.scalar_one_or_none(), 1)
|
|
|
|
def test_scalar_one_or_none_none(self):
|
|
result = self._fixture(num_rows=0)
|
|
|
|
eq_(result.scalar_one_or_none(), None)
|
|
|
|
def test_one_or_none_none(self):
|
|
result = self._fixture(num_rows=0)
|
|
|
|
eq_(result.one_or_none(), None)
|
|
|
|
def test_one_raise_mutiple(self):
|
|
result = self._fixture(num_rows=2)
|
|
|
|
assert_raises_message(
|
|
exc.MultipleResultsFound,
|
|
"Multiple rows were found when exactly one was required",
|
|
result.one,
|
|
)
|
|
|
|
def test_one_or_none_raise_multiple(self):
|
|
result = self._fixture(num_rows=2)
|
|
|
|
assert_raises_message(
|
|
exc.MultipleResultsFound,
|
|
"Multiple rows were found when one or none was required",
|
|
result.one_or_none,
|
|
)
|
|
|
|
def test_scalar(self):
|
|
result = self._fixture()
|
|
|
|
eq_(result.scalar(), 1)
|
|
|
|
# note this is a behavior change in 1.4.27 due to
|
|
# adding a real result.close() to Result, previously this would
|
|
# return an empty list. this is already the
|
|
# behavior with CursorResult, but was mis-implemented for
|
|
# other non-cursor result sets.
|
|
assert_raises(exc.ResourceClosedError, result.all)
|
|
|
|
def test_partition(self):
|
|
result = self._fixture()
|
|
|
|
r = []
|
|
for partition in result.partitions(2):
|
|
r.append(list(partition))
|
|
eq_(r, [[(1, 1, 1), (2, 1, 2)], [(1, 3, 2), (4, 1, 2)]])
|
|
|
|
eq_(result.all(), [])
|
|
|
|
def test_partition_unique_yield_per(self):
|
|
result = self._fixture()
|
|
|
|
r = []
|
|
for partition in result.unique().yield_per(2).partitions():
|
|
r.append(list(partition))
|
|
eq_(r, [[(1, 1, 1), (2, 1, 2)], [(1, 3, 2), (4, 1, 2)]])
|
|
|
|
eq_(result.all(), [])
|
|
|
|
def test_partition_yield_per(self):
|
|
result = self._fixture()
|
|
|
|
r = []
|
|
for partition in result.yield_per(2).partitions():
|
|
r.append(list(partition))
|
|
eq_(r, [[(1, 1, 1), (2, 1, 2)], [(1, 3, 2), (4, 1, 2)]])
|
|
|
|
eq_(result.all(), [])
|
|
|
|
def test_columns(self):
|
|
result = self._fixture()
|
|
|
|
result = result.columns("b", "c")
|
|
eq_(result.keys(), ["b", "c"])
|
|
eq_(result.all(), [(1, 1), (1, 2), (3, 2), (1, 2)])
|
|
|
|
def test_columns_ints(self):
|
|
result = self._fixture()
|
|
|
|
eq_(result.columns(1, -2).all(), [(1, 1), (1, 1), (3, 3), (1, 1)])
|
|
|
|
def test_columns_again(self):
|
|
result = self._fixture()
|
|
|
|
eq_(
|
|
result.columns("b", "c", "a").columns(1, 2).all(),
|
|
[(1, 1), (2, 2), (2, 1), (2, 4)],
|
|
)
|
|
|
|
def test_mappings(self):
|
|
result = self._fixture()
|
|
|
|
eq_(
|
|
[dict(r) for r in result.mappings()],
|
|
[
|
|
{"a": 1, "b": 1, "c": 1},
|
|
{"a": 2, "b": 1, "c": 2},
|
|
{"a": 1, "b": 3, "c": 2},
|
|
{"a": 4, "b": 1, "c": 2},
|
|
],
|
|
)
|
|
|
|
def test_columns_with_mappings(self):
|
|
result = self._fixture()
|
|
eq_(
|
|
[dict(r) for r in result.columns("b", "c").mappings().all()],
|
|
[
|
|
{"b": 1, "c": 1},
|
|
{"b": 1, "c": 2},
|
|
{"b": 3, "c": 2},
|
|
{"b": 1, "c": 2},
|
|
],
|
|
)
|
|
|
|
def test_mappings_with_columns(self):
|
|
result = self._fixture()
|
|
|
|
m1 = result.mappings().columns("b", "c")
|
|
|
|
eq_(m1.fetchmany(2), [{"b": 1, "c": 1}, {"b": 1, "c": 2}])
|
|
|
|
# no slice here
|
|
eq_(result.fetchone(), (1, 3, 2))
|
|
|
|
# still slices
|
|
eq_(m1.fetchone(), {"b": 1, "c": 2})
|
|
|
|
def test_scalar_none_iterate(self):
|
|
result = self._fixture(
|
|
data=[
|
|
(1, None, 2),
|
|
(3, 4, 5),
|
|
(3, None, 5),
|
|
(3, None, 5),
|
|
(3, 4, 5),
|
|
]
|
|
)
|
|
|
|
result = result.scalars(1)
|
|
eq_(list(result), [None, 4, None, None, 4])
|
|
|
|
def test_scalar_none_many(self):
|
|
result = self._fixture(
|
|
data=[
|
|
(1, None, 2),
|
|
(3, 4, 5),
|
|
(3, None, 5),
|
|
(3, None, 5),
|
|
(3, 4, 5),
|
|
]
|
|
)
|
|
|
|
result = result.scalars(1)
|
|
|
|
eq_(result.fetchmany(3), [None, 4, None])
|
|
eq_(result.fetchmany(5), [None, 4])
|
|
|
|
def test_scalar_none_all(self):
|
|
result = self._fixture(
|
|
data=[
|
|
(1, None, 2),
|
|
(3, 4, 5),
|
|
(3, None, 5),
|
|
(3, None, 5),
|
|
(3, 4, 5),
|
|
]
|
|
)
|
|
|
|
result = result.scalars(1)
|
|
eq_(result.all(), [None, 4, None, None, 4])
|
|
|
|
def test_scalar_none_all_unique(self):
|
|
result = self._fixture(
|
|
data=[
|
|
(1, None, 2),
|
|
(3, 4, 5),
|
|
(3, None, 5),
|
|
(3, None, 5),
|
|
(3, 4, 5),
|
|
]
|
|
)
|
|
|
|
result = result.scalars(1).unique()
|
|
eq_(result.all(), [None, 4])
|
|
|
|
def test_scalar_only_on_filter_w_unique(self):
|
|
# test a mixture of the "real" result and the
|
|
# scalar filter, where scalar has unique and real result does not.
|
|
|
|
# this is new as of [ticket:5503] where we have created
|
|
# ScalarResult / MappingResult "filters" that don't modify
|
|
# the Result
|
|
result = self._fixture(
|
|
data=[
|
|
(1, 1, 2),
|
|
(3, 4, 5),
|
|
(1, 1, 2),
|
|
(3, None, 5),
|
|
(3, 4, 5),
|
|
(3, None, 5),
|
|
]
|
|
)
|
|
|
|
# result is non-unique. u_s is unique on column 0
|
|
u_s = result.scalars(0).unique()
|
|
|
|
eq_(next(u_s), 1) # unique value 1 from first row
|
|
eq_(next(result), (3, 4, 5)) # second row
|
|
eq_(next(u_s), 3) # skip third row, return 3 for fourth row
|
|
eq_(next(result), (3, 4, 5)) # non-unique fifth row
|
|
eq_(u_s.all(), []) # unique set is done because only 3 is left
|
|
|
|
def test_scalar_none_one_w_unique(self):
|
|
result = self._fixture(data=[(1, None, 2)])
|
|
|
|
result = result.scalars(1).unique()
|
|
|
|
# one is returning None, see?
|
|
eq_(result.one(), None)
|
|
|
|
def test_scalar_none_one_or_none_w_unique(self):
|
|
result = self._fixture(data=[(1, None, 2)])
|
|
|
|
result = result.scalars(1).unique()
|
|
|
|
# the orm.Query can actually do this right now, so we sort of
|
|
# have to allow for this unforuntately, unless we want to raise?
|
|
eq_(result.one_or_none(), None)
|
|
|
|
def test_scalar_one_w_unique(self):
|
|
result = self._fixture(data=[(1, None, 2)])
|
|
|
|
result = result.unique()
|
|
|
|
eq_(result.scalar_one(), 1)
|
|
|
|
def test_scalars_one_w_unique(self):
|
|
result = self._fixture(data=[(1, None, 2)])
|
|
|
|
result = result.unique()
|
|
|
|
eq_(result.scalars().one(), 1)
|
|
|
|
def test_scalar_none_first(self):
|
|
result = self._fixture(data=[(1, None, 2)])
|
|
|
|
result = result.scalars(1).unique()
|
|
eq_(result.first(), None)
|
|
|
|
def test_freeze(self):
|
|
result = self._fixture()
|
|
|
|
frozen = result.freeze()
|
|
|
|
r1 = frozen()
|
|
eq_(r1.fetchall(), [(1, 1, 1), (2, 1, 2), (1, 3, 2), (4, 1, 2)])
|
|
eq_(r1.fetchall(), [])
|
|
|
|
r2 = frozen()
|
|
eq_(r1.fetchall(), [])
|
|
eq_(r2.fetchall(), [(1, 1, 1), (2, 1, 2), (1, 3, 2), (4, 1, 2)])
|
|
eq_(r2.fetchall(), [])
|
|
|
|
def test_columns_unique_freeze(self):
|
|
result = self._fixture()
|
|
|
|
result = result.columns("b", "c").unique()
|
|
|
|
frozen = result.freeze()
|
|
|
|
r1 = frozen()
|
|
eq_(r1.fetchall(), [(1, 1), (1, 2), (3, 2)])
|
|
|
|
def test_columns_freeze(self):
|
|
result = self._fixture()
|
|
|
|
result = result.columns("b", "c")
|
|
|
|
frozen = result.freeze()
|
|
|
|
r1 = frozen()
|
|
eq_(r1.fetchall(), [(1, 1), (1, 2), (3, 2), (1, 2)])
|
|
|
|
r2 = frozen().unique()
|
|
eq_(r2.fetchall(), [(1, 1), (1, 2), (3, 2)])
|
|
|
|
def test_scalars_freeze(self):
|
|
result = self._fixture()
|
|
|
|
frozen = result.freeze()
|
|
|
|
r1 = frozen()
|
|
eq_(r1.scalars(1).fetchall(), [1, 1, 3, 1])
|
|
|
|
r2 = frozen().scalars(1).unique()
|
|
eq_(r2.fetchall(), [1, 3])
|
|
|
|
|
|
class MergeResultTest(fixtures.TestBase):
|
|
@testing.fixture
|
|
def merge_fixture(self):
|
|
|
|
r1 = result.IteratorResult(
|
|
result.SimpleResultMetaData(["user_id", "user_name"]),
|
|
iter([(7, "u1"), (8, "u2")]),
|
|
)
|
|
r2 = result.IteratorResult(
|
|
result.SimpleResultMetaData(["user_id", "user_name"]),
|
|
iter([(9, "u3")]),
|
|
)
|
|
r3 = result.IteratorResult(
|
|
result.SimpleResultMetaData(["user_id", "user_name"]),
|
|
iter([(10, "u4"), (11, "u5")]),
|
|
)
|
|
r4 = result.IteratorResult(
|
|
result.SimpleResultMetaData(["user_id", "user_name"]),
|
|
iter([(12, "u6")]),
|
|
)
|
|
|
|
return r1, r2, r3, r4
|
|
|
|
@testing.fixture
|
|
def dupe_fixture(self):
|
|
|
|
r1 = result.IteratorResult(
|
|
result.SimpleResultMetaData(["x", "y", "z"]),
|
|
iter([(1, 2, 1), (2, 2, 1)]),
|
|
)
|
|
r2 = result.IteratorResult(
|
|
result.SimpleResultMetaData(["x", "y", "z"]),
|
|
iter([(3, 1, 2), (3, 3, 3)]),
|
|
)
|
|
|
|
return r1, r2
|
|
|
|
def test_merge_results(self, merge_fixture):
|
|
r1, r2, r3, r4 = merge_fixture
|
|
|
|
result = r1.merge(r2, r3, r4)
|
|
|
|
eq_(result.keys(), ["user_id", "user_name"])
|
|
row = result.fetchone()
|
|
eq_(row, (7, "u1"))
|
|
result.close()
|
|
|
|
def test_fetchall(self, merge_fixture):
|
|
r1, r2, r3, r4 = merge_fixture
|
|
|
|
result = r1.merge(r2, r3, r4)
|
|
eq_(
|
|
result.fetchall(),
|
|
[
|
|
(7, "u1"),
|
|
(8, "u2"),
|
|
(9, "u3"),
|
|
(10, "u4"),
|
|
(11, "u5"),
|
|
(12, "u6"),
|
|
],
|
|
)
|
|
|
|
def test_first(self, merge_fixture):
|
|
r1, r2, r3, r4 = merge_fixture
|
|
|
|
result = r1.merge(r2, r3, r4)
|
|
eq_(
|
|
result.first(),
|
|
(7, "u1"),
|
|
)
|
|
|
|
def test_columns(self, merge_fixture):
|
|
r1, r2, r3, r4 = merge_fixture
|
|
|
|
result = r1.merge(r2, r3, r4)
|
|
eq_(
|
|
result.columns("user_name").fetchmany(4),
|
|
[("u1",), ("u2",), ("u3",), ("u4",)],
|
|
)
|
|
result.close()
|
|
|
|
def test_merge_scalars(self, merge_fixture):
|
|
r1, r2, r3, r4 = merge_fixture
|
|
|
|
for r in (r1, r2, r3, r4):
|
|
r.scalars(0)
|
|
|
|
result = r1.merge(r2, r3, r4)
|
|
|
|
eq_(result.scalars(0).all(), [7, 8, 9, 10, 11, 12])
|
|
|
|
def test_merge_unique(self, dupe_fixture):
|
|
r1, r2 = dupe_fixture
|
|
|
|
r1.scalars("y")
|
|
r2.scalars("y")
|
|
result = r1.merge(r2)
|
|
|
|
# uniqued 2, 2, 1, 3
|
|
eq_(result.scalars("y").unique().all(), [2, 1, 3])
|
|
|
|
def test_merge_preserve_unique(self, dupe_fixture):
|
|
r1, r2 = dupe_fixture
|
|
|
|
r1.unique().scalars("y")
|
|
r2.unique().scalars("y")
|
|
result = r1.merge(r2)
|
|
|
|
# unique takes place
|
|
eq_(result.scalars("y").all(), [2, 1, 3])
|
|
|
|
|
|
class OnlyScalarsTest(fixtures.TestBase):
|
|
"""the chunkediterator supports "non tuple mode", where we bypass
|
|
the expense of generating rows when we have only scalar values.
|
|
|
|
"""
|
|
|
|
@testing.fixture
|
|
def no_tuple_fixture(self):
|
|
data = [(1, 1, 1), (2, 1, 2), (1, 1, 1), (1, 3, 2), (4, 1, 2)]
|
|
|
|
def chunks(num):
|
|
while data:
|
|
rows = data[0:num]
|
|
data[:] = []
|
|
|
|
yield [row[0] for row in rows]
|
|
|
|
return chunks
|
|
|
|
@testing.fixture
|
|
def no_tuple_one_fixture(self):
|
|
data = [(1, 1, 1)]
|
|
|
|
def chunks(num):
|
|
while data:
|
|
rows = data[0:num]
|
|
data[:] = []
|
|
|
|
yield [row[0] for row in rows]
|
|
|
|
return chunks
|
|
|
|
@testing.fixture
|
|
def normal_fixture(self):
|
|
data = [(1, 1, 1), (2, 1, 2), (1, 1, 1), (1, 3, 2), (4, 1, 2)]
|
|
|
|
def chunks(num):
|
|
while data:
|
|
rows = data[0:num]
|
|
data[:] = []
|
|
|
|
yield [row[0] for row in rows]
|
|
|
|
return chunks
|
|
|
|
def test_scalar_mode_columns0_mapping(self, no_tuple_fixture):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, no_tuple_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
r = r.columns(0).mappings()
|
|
eq_(
|
|
list(r),
|
|
[{"a": 1}, {"a": 2}, {"a": 1}, {"a": 1}, {"a": 4}],
|
|
)
|
|
|
|
def test_scalar_mode_columns0_plain(self, no_tuple_fixture):
|
|
"""test #7953"""
|
|
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, no_tuple_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
r = r.columns(0)
|
|
eq_(
|
|
list(r),
|
|
[(1,), (2,), (1,), (1,), (4,)],
|
|
)
|
|
|
|
def test_scalar_mode_scalars0(self, no_tuple_fixture):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, no_tuple_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
r = r.scalars(0)
|
|
eq_(
|
|
list(r),
|
|
[1, 2, 1, 1, 4],
|
|
)
|
|
|
|
def test_scalar_mode_but_accessed_nonscalar_result(self, no_tuple_fixture):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, no_tuple_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
s1 = r.scalars()
|
|
|
|
eq_(r.fetchone(), (1,))
|
|
|
|
eq_(s1.all(), [2, 1, 1, 4])
|
|
|
|
def test_scalar_mode_scalars_all(self, no_tuple_fixture):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, no_tuple_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
r = r.scalars()
|
|
|
|
eq_(r.all(), [1, 2, 1, 1, 4])
|
|
|
|
def test_scalar_mode_mfiltered_unique_rows_all(self, no_tuple_fixture):
|
|
metadata = result.SimpleResultMetaData(
|
|
["a", "b", "c"], _unique_filters=[int]
|
|
)
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata,
|
|
no_tuple_fixture,
|
|
source_supports_scalars=True,
|
|
)
|
|
|
|
r = r.unique()
|
|
|
|
eq_(r.all(), [(1,), (2,), (4,)])
|
|
|
|
@testing.combinations(
|
|
lambda r: r.scalar(),
|
|
lambda r: r.scalar_one(),
|
|
lambda r: r.scalar_one_or_none(),
|
|
argnames="get",
|
|
)
|
|
def test_unique_scalar_accessors(self, no_tuple_one_fixture, get):
|
|
metadata = result.SimpleResultMetaData(
|
|
["a", "b", "c"], _unique_filters=[int]
|
|
)
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata,
|
|
no_tuple_one_fixture,
|
|
source_supports_scalars=True,
|
|
)
|
|
|
|
r = r.unique()
|
|
|
|
eq_(get(r), 1)
|
|
|
|
def test_scalar_mode_mfiltered_unique_mappings_all(self, no_tuple_fixture):
|
|
metadata = result.SimpleResultMetaData(
|
|
["a", "b", "c"], _unique_filters=[int]
|
|
)
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata,
|
|
no_tuple_fixture,
|
|
source_supports_scalars=True,
|
|
)
|
|
|
|
r = r.unique()
|
|
|
|
eq_(r.mappings().all(), [{"a": 1}, {"a": 2}, {"a": 4}])
|
|
|
|
def test_scalar_mode_mfiltered_unique_scalars_all(self, no_tuple_fixture):
|
|
metadata = result.SimpleResultMetaData(
|
|
["a", "b", "c"], _unique_filters=[int]
|
|
)
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata,
|
|
no_tuple_fixture,
|
|
source_supports_scalars=True,
|
|
)
|
|
|
|
r = r.scalars().unique()
|
|
|
|
eq_(r.all(), [1, 2, 4])
|
|
|
|
def test_scalar_mode_unique_scalars_all(self, no_tuple_fixture):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, no_tuple_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
r = r.unique().scalars()
|
|
|
|
eq_(r.all(), [1, 2, 4])
|
|
|
|
def test_scalar_mode_scalars_fetchmany(self, normal_fixture):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, normal_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
r = r.scalars()
|
|
eq_(list(r.partitions(2)), [[1, 2], [1, 1], [4]])
|
|
|
|
def test_scalar_mode_unique_scalars_fetchmany(self, normal_fixture):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, normal_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
r = r.scalars().unique()
|
|
eq_(list(r.partitions(2)), [[1, 2], [4]])
|
|
|
|
def test_scalar_mode_unique_tuples_all(self, normal_fixture):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, normal_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
r = r.unique()
|
|
|
|
eq_(r.all(), [(1,), (2,), (4,)])
|
|
|
|
def test_scalar_mode_tuples_all(self, normal_fixture):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, normal_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
eq_(r.all(), [(1,), (2,), (1,), (1,), (4,)])
|
|
|
|
def test_scalar_mode_scalars_iterate(self, no_tuple_fixture):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, no_tuple_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
r = r.scalars()
|
|
|
|
eq_(list(r), [1, 2, 1, 1, 4])
|
|
|
|
def test_scalar_mode_tuples_iterate(self, normal_fixture):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, normal_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
eq_(list(r), [(1,), (2,), (1,), (1,), (4,)])
|
|
|
|
@testing.combinations(
|
|
lambda r: r.one(),
|
|
lambda r: r.first(),
|
|
lambda r: r.one_or_none(),
|
|
argnames="get",
|
|
)
|
|
def test_scalar_mode_first(self, no_tuple_one_fixture, get):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, no_tuple_one_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
eq_(get(r), (1,))
|
|
|
|
@testing.combinations(
|
|
lambda r: r.scalar(),
|
|
lambda r: r.scalar_one(),
|
|
lambda r: r.scalar_one_or_none(),
|
|
argnames="get",
|
|
)
|
|
def test_scalar_mode_scalar_one(self, no_tuple_one_fixture, get):
|
|
metadata = result.SimpleResultMetaData(["a", "b", "c"])
|
|
|
|
r = result.ChunkedIteratorResult(
|
|
metadata, no_tuple_one_fixture, source_supports_scalars=True
|
|
)
|
|
|
|
eq_(get(r), 1)
|