self._key_to_index = key_to_index
if processors:
- self._data = tuple(
- [
- proc(value) if proc else value
- for proc, value in zip(processors, data)
- ]
- )
+ self._data = _apply_processors(processors, data)
else:
self._data = tuple(data)
def __getattr__(self, name):
return self._get_by_key_impl(name, 1)
+ def _to_tuple_instance(self):
+ return self._data
+
+
+cdef tuple _apply_processors(proc, data):
+ res = []
+ for i in range(len(proc)):
+ p = proc[i]
+ if p is None:
+ res.append(data[i])
+ else:
+ res.append(p(data[i]))
+ return tuple(res)
+
def rowproxy_reconstructor(cls, state):
obj = cls.__new__(cls)
return obj
-def tuplegetter(*indexes):
- it = operator.itemgetter(*indexes)
+cdef int is_contiguous(tuple indexes):
+ cdef int i
+ for i in range(1, len(indexes)):
+ if indexes[i-1] != indexes[i] -1:
+ return 0
+ return 1
+
- if len(indexes) > 1:
- return it
+def tuplegetter(*indexes):
+ if len(indexes) == 1 or is_contiguous(indexes) != 0:
+ # slice form is faster but returns a list if input is list
+ return operator.itemgetter(slice(indexes[0], indexes[-1] + 1))
else:
- return lambda row: (it(row),)
+ return operator.itemgetter(*indexes)
pass
self._parent._key_not_found(name, True)
+ def _to_tuple_instance(self) -> Tuple[Any, ...]:
+ return self._data
+
# This reconstructor is necessary so that pickles with the Cy extension or
# without use the same Binary format.
def tuplegetter(*indexes: int) -> _TupleGetterType:
- it = operator.itemgetter(*indexes)
-
- if len(indexes) > 1:
- return it
- else:
- return lambda row: (it(row),)
+ if len(indexes) != 1:
+ for i in range(1, len(indexes)):
+ if indexes[i - 1] != indexes[i] - 1:
+ return operator.itemgetter(*indexes)
+ # slice form is faster but returns a list if input is list
+ return operator.itemgetter(slice(indexes[0], indexes[-1] + 1))
import collections
import functools
+import operator
import typing
from typing import Any
from typing import cast
# getter assuming no transformations will be called as this
# is the most common case
- if echo:
- log = self.context.connection._log_debug
-
- def _log_row(row):
- log("Row %r", sql_util._repr_row(row))
- return row
-
- self._row_logging_fn = log_row = _log_row
- else:
- log_row = None
-
metadata = self._init_metadata(context, cursor_description)
_make_row = functools.partial(
Row,
metadata,
- metadata._processors,
+ metadata._effective_processors,
metadata._key_to_index,
)
- if log_row:
+
+ if context._num_sentinel_cols:
+ sentinel_filter = operator.itemgetter(
+ slice(-context._num_sentinel_cols)
+ )
+
+ def _sliced_row(raw_data):
+ return _make_row(sentinel_filter(raw_data))
+
+ sliced_row = _sliced_row
+ else:
+ sliced_row = _make_row
+
+ if echo:
+ log = self.context.connection._log_debug
+
+ def _log_row(row):
+ log("Row %r", sql_util._repr_row(row))
+ return row
+
+ self._row_logging_fn = _log_row
def _make_row_2(row):
- made_row = _make_row(row)
- assert log_row is not None
- log_row(made_row)
- return made_row
+ return _log_row(sliced_row(row))
make_row = _make_row_2
else:
- make_row = _make_row
+ make_row = sliced_row
self._set_memoized_attribute("_row_getter", make_row)
else:
+ assert context._num_sentinel_cols == 0
self._metadata = self._no_result_metadata
def _init_metadata(self, context, cursor_description):
strategy = _cursor._NO_CURSOR_DML
elif self._num_sentinel_cols:
assert self.execute_style is ExecuteStyle.INSERTMANYVALUES
- if cursor_description:
- # strip out the sentinel columns from cursor description
- cursor_description = cursor_description[
- 0 : -(self._num_sentinel_cols)
- ]
+ # strip out the sentinel columns from cursor description
+ # a similar logic is done to the rows only in CursorResult
+ cursor_description = cursor_description[
+ 0 : -self._num_sentinel_cols
+ ]
result: _cursor.CursorResult[Any] = _cursor.CursorResult(
self, strategy, cursor_description
from ..sql.base import HasMemoized
from ..sql.base import InPlaceGenerative
from ..util import HasMemoized_ro_memoized_attribute
+from ..util import NONE_SET
from ..util._has_cy import HAS_CYEXTENSION
from ..util.typing import Literal
from ..util.typing import Self
_InterimSupportsScalarsRowType = Union[Row, Any]
_ProcessorsType = Sequence[Optional["_ResultProcessorType[Any]"]]
-_TupleGetterType = Callable[[Sequence[Any]], Tuple[Any, ...]]
+_TupleGetterType = Callable[[Sequence[Any]], Sequence[Any]]
_UniqueFilterType = Callable[[Any], Any]
_UniqueFilterStateType = Tuple[Set[Any], Optional[_UniqueFilterType]]
else:
self._key_fallback(key, None)
+ @property
+ def _effective_processors(self) -> Optional[_ProcessorsType]:
+ if not self._processors or NONE_SET.issuperset(self._processors):
+ return None
+ else:
+ return self._processors
+
class RMKeyView(typing.KeysView[Any]):
__slots__ = ("_parent", "_keys")
) -> Callable[[Iterable[Any]], Row[Any]]:
parent = SimpleResultMetaData(fields, extra)
return functools.partial(
- Row, parent, parent._processors, parent._key_to_index
+ Row, parent, parent._effective_processors, parent._key_to_index
)
def process_row( # type: ignore
metadata: ResultMetaData,
- processors: _ProcessorsType,
+ processors: Optional[_ProcessorsType],
key_to_index: Mapping[_KeyType, int],
scalar_obj: Any,
) -> Row[Any]:
metadata = self._metadata
key_to_index = metadata._key_to_index
- processors = metadata._processors
+ processors = metadata._effective_processors
tf = metadata._tuplefilter
if tf and not real_result._source_supports_scalars:
process_row, metadata, processors, key_to_index
)
- fns: Tuple[Any, ...] = ()
-
if real_result._row_logging_fn:
- fns = (real_result._row_logging_fn,)
- else:
- fns = ()
-
- if fns:
+ _log_row = real_result._row_logging_fn
_make_row = make_row
def make_row(row: _InterimRowType[Row[Any]]) -> _R:
- interim_row = _make_row(row)
- for fn in fns:
- interim_row = fn(interim_row)
- return interim_row # type: ignore
+ return _log_row(_make_row(row)) # type: ignore
return make_row
if TYPE_CHECKING:
from .result import _KeyType
from .result import RMKeyView
- from ..sql.type_api import _ResultProcessorType
+ from .result import _ProcessorsType
_T = TypeVar("_T", bound=Any)
_TP = TypeVar("_TP", bound=Tuple[Any, ...])
return RowMapping(self._parent, None, self._key_to_index, self._data)
def _filter_on_values(
- self, filters: Optional[Sequence[Optional[_ResultProcessorType[Any]]]]
+ self, processor: Optional[_ProcessorsType]
) -> Row[Any]:
- return Row(self._parent, filters, self._key_to_index, self._data)
+ return Row(self._parent, processor, self._key_to_index, self._data)
if not TYPE_CHECKING:
def _op(self, other: Any, op: Callable[[Any, Any], bool]) -> bool:
return (
- op(tuple(self), tuple(other))
+ op(self._to_tuple_instance(), other._to_tuple_instance())
if isinstance(other, Row)
- else op(tuple(self), other)
+ else op(self._to_tuple_instance(), other)
)
__hash__ = BaseRow.__hash__
row3 = loads(state2)
is_true(isinstance(row3, dump_cls))
+ def test_processors(self):
+ parent = result.SimpleResultMetaData(["a", "b", "c", "d"])
+ data = (1, 99, "42", "foo")
+ row_none = result.Row(parent, None, parent._key_to_index, data)
+ eq_(row_none._to_tuple_instance(), data)
+ row_all_p = result.Row(
+ parent, [str, float, int, str.upper], parent._key_to_index, data
+ )
+ eq_(row_all_p._to_tuple_instance(), ("1", 99.0, 42, "FOO"))
+ row_some_p = result.Row(
+ parent, [None, str, None, str.upper], parent._key_to_index, data
+ )
+ eq_(row_some_p._to_tuple_instance(), (1, "99", "42", "FOO"))
+ row_shorter = result.Row(
+ parent, [None, str], parent._key_to_index, data
+ )
+ eq_(row_shorter._to_tuple_instance(), (1, "99"))
+
+ def test_tuplegetter(self):
+ data = list(range(10, 20))
+ eq_(result.tuplegetter(1)(data), [11])
+ eq_(result.tuplegetter(1, 9, 3)(data), (11, 19, 13))
+ eq_(result.tuplegetter(2, 3, 4)(data), [12, 13, 14])
+
class ResultTest(fixtures.TestBase):
def _fixture(
class TupleGetter(Case):
+ NUMBER = 2_000_000
+
@staticmethod
def python():
from sqlalchemy.engine._py_row import tuplegetter
self.tuple = tuple(range(1000))
self.tg_inst = self.impl_tg(42)
self.tg_inst_m = self.impl_tg(42, 420, 99, 9, 1)
-
- class MockRow:
- def __init__(self, data):
- self.data = data
-
- def __getitem__(self, index):
- # called by python
- return self.data[index]
-
- def _get_by_key_impl_mapping(self, index):
- # called by c
- return self.data[index]
-
- self.row = MockRow(self.tuple)
+ self.tg_inst_seq = self.impl_tg(*range(70, 75))
@classmethod
def update_results(cls, results):
def tuplegetter_many(self):
self.tg_inst_m(self.tuple)
+ @test_case
+ def tuplegetter_seq(self):
+ self.tg_inst_seq(self.tuple)
+
@test_case
def tuplegetter_new_one(self):
self.impl_tg(42)(self.tuple)
def tuplegetter_new_many(self):
self.impl_tg(42, 420, 99, 9, 1)(self.tuple)
+ @test_case
+ def tuplegetter_new_seq(self):
+ self.impl_tg(40, 41, 42, 43, 44)(self.tuple)
+
class BaseRow(Case):
@staticmethod
self.row_state = self.row.__getstate__()
self.row_long_state = self.row_long.__getstate__()
+ assert len(ascii_letters) == 52
+ self.parent_proc = SimpleResultMetaData(
+ tuple(ascii_letters),
+ _processors=[None, int, float, None, str] * 10, # cut the last 2
+ )
+ self.row_proc_args = (
+ self.parent_proc,
+ self.parent_proc._processors,
+ self.parent_proc._key_to_index,
+ tuple(range(len(ascii_letters))),
+ )
+
+ self.parent_proc_none = SimpleResultMetaData(
+ tuple(ascii_letters), _processors=[None] * 52
+ )
+ self.row_proc_none_args = (
+ self.parent_proc_none,
+ # NOTE: usually the code calls _effective_processors that returns
+ # None for this case of all None.
+ self.parent_proc_none._processors,
+ self.parent_proc_none._key_to_index,
+ tuple(range(len(ascii_letters))),
+ )
+
@classmethod
def update_results(cls, results):
cls._divide_results(results, "c", "python", "c / py")
self.Row(*self.row_args)
self.Row(*self.row_long_args)
+ @test_case
+ def base_row_new_proc(self):
+ self.impl(*self.row_proc_args)
+
+ @test_case
+ def row_new_proc(self):
+ self.Row(*self.row_proc_args)
+
+ @test_case
+ def brow_new_proc_none(self):
+ self.impl(*self.row_proc_none_args)
+
+ @test_case
+ def row_new_proc_none(self):
+ self.Row(*self.row_proc_none_args)
+
@test_case
def row_dumps(self):
self.row.__getstate__()
test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_cextensions 2592
test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_nocextensions 25595
test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_cextensions 2539
-test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 15600
+test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 14614
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-0]
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_cextensions 14
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_nocextensions 16
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_cextensions 14
-test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 16
+test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 15
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2]
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_cextensions 14
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_nocextensions 16
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_cextensions 14
-test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 16
+test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 15
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1]
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_cextensions 17
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_nocextensions 19
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_cextensions 17
-test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 19
+test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1] x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 18
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string
test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_cextensions 291
test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_nocextensions 6291
test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_cextensions 257
-test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 6257
+test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 5277
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode
test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_cextensions 291
test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_nocextensions 6291
test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_cextensions 257
-test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 6257
+test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 5277
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_string
test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_cextensions 585
test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_nocextensions 6589
test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_cextensions 532
-test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 6536
+test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 5605
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_unicode
test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_cextensions 585
test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.10_postgresql_psycopg2_dbapiunicode_nocextensions 6589
test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_cextensions 532
-test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 6536
+test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 5605