return [params]
else:
raise exc.ArgumentError("mapping or sequence expected for parameters")
+
+cdef class prefix_anon_map(dict):
+ def __missing__(self, str key):
+ cdef str derived
+ cdef int anonymous_counter
+ cdef dict self_dict = self
+
+ derived = key.split(" ", 1)[1]
+
+ anonymous_counter = self_dict.get(derived, 1)
+ self_dict[derived] = anonymous_counter + 1
+ value = f"{derived}_{anonymous_counter}"
+ self_dict[key] = value
+ return value
+
+
+cdef class cache_anon_map(dict):
+ cdef int _index
+
+ def __init__(self):
+ self._index = 0
+
+ def get_anon(self, obj):
+ cdef long idself
+ cdef str id_
+ cdef dict self_dict = self
+
+ idself = id(obj)
+ if idself in self_dict:
+ return self_dict[idself], True
+ else:
+ id_ = self.__missing__(idself)
+ return id_, False
+
+ def __missing__(self, key):
+ cdef str val
+ cdef dict self_dict = self
+
+ self_dict[key] = val = str(self._index)
+ self._index += 1
+ return val
+
--- /dev/null
+# sql/_py_util.py
+# Copyright (C) 2005-2021 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+
+class prefix_anon_map(dict):
+ """A map that creates new keys for missing key access.
+
+ Considers keys of the form "<ident> <name>" to produce
+ new symbols "<name>_<index>", where "index" is an incrementing integer
+ corresponding to <name>.
+
+ Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which
+ is otherwise usually used for this type of operation.
+
+ """
+
+ def __missing__(self, key):
+ (ident, derived) = key.split(" ", 1)
+ anonymous_counter = self.get(derived, 1)
+ self[derived] = anonymous_counter + 1
+ value = f"{derived}_{anonymous_counter}"
+ self[key] = value
+ return value
+
+
+class cache_anon_map(dict):
+ """A map that creates new keys for missing key access.
+
+ Produces an incrementing sequence given a series of unique keys.
+
+ This is similar to the compiler prefix_anon_map class although simpler.
+
+ Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which
+ is otherwise usually used for this type of operation.
+
+ """
+
+ _index = 0
+
+ def get_anon(self, object_):
+
+ idself = id(object_)
+ if idself in self:
+ return self[idself], True
+ else:
+ # inline of __missing__
+ self[idself] = id_ = str(self._index)
+ self._index += 1
+
+ return id_, False
+
+ def __missing__(self, key):
+ self[key] = val = str(self._index)
+ self._index += 1
+ return val
from ..util import HasMemoized
from ..util import hybridmethod
+try:
+ from sqlalchemy.cyextension.util import prefix_anon_map # noqa
+except ImportError:
+ from ._py_util import prefix_anon_map # noqa
+
+
coercions = None
elements = None
type_api = None
return self._execution_options
-class prefix_anon_map(dict):
- """A map that creates new keys for missing key access.
-
- Considers keys of the form "<ident> <name>" to produce
- new symbols "<name>_<index>", where "index" is an incrementing integer
- corresponding to <name>.
-
- Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which
- is otherwise usually used for this type of operation.
-
- """
-
- def __missing__(self, key):
- (ident, derived) = key.split(" ", 1)
- anonymous_counter = self.get(derived, 1)
- self[derived] = anonymous_counter + 1
- value = derived + "_" + str(anonymous_counter)
- self[key] = value
- return value
-
-
class SchemaEventTarget:
"""Base class for elements that are the targets of :class:`.DDLEvents`
events.
anon_map[NO_CACHE] = True
return None
- idself = id(self)
- if idself in anon_map:
- return (anon_map[idself], self.__class__)
- else:
- # inline of
- # id_ = anon_map[idself]
- anon_map[idself] = id_ = str(anon_map.index)
- anon_map.index += 1
+ id_, found = anon_map.get_anon(self)
+ if found:
+ return (id_, self.__class__)
if bindparams is not None:
bindparams.append(self)
from ..inspection import inspect
from ..util import HasMemoized
+try:
+ from sqlalchemy.cyextension.util import cache_anon_map as anon_map # noqa
+except ImportError:
+ from ._py_util import cache_anon_map as anon_map # noqa
+
+
SKIP_TRAVERSE = util.symbol("skip_traverse")
COMPARE_FAILED = False
COMPARE_SUCCEEDED = True
"""
- idself = id(self)
cls = self.__class__
- if idself in anon_map:
- return (anon_map[idself], cls)
- else:
- # inline of
- # id_ = anon_map[idself]
- anon_map[idself] = id_ = str(anon_map.index)
- anon_map.index += 1
+ id_, found = anon_map.get_anon(self)
+ if found:
+ return (id_, cls)
try:
dispatcher = cls.__dict__["_generated_cache_key_traversal"]
return name
-class anon_map(dict):
- """A map that creates new keys for missing key access.
-
- Produces an incrementing sequence given a series of unique keys.
-
- This is similar to the compiler prefix_anon_map class although simpler.
-
- Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which
- is otherwise usually used for this type of operation.
-
- """
-
- def __init__(self):
- self.index = 0
-
- def __missing__(self, key):
- self[key] = val = str(self.index)
- self.index += 1
- return val
-
-
class TraversalComparatorStrategy(InternalTraversal, util.MemoizedSlots):
__slots__ = "stack", "cache", "anon_map"
import abc
import configparser
import logging
+import os
import re
import sys
sys.modules["decimal"] = cdecimal
+@post
+def __ensure_cext(opt, file_config):
+ if os.environ.get("REQUIRE_SQLALCHEMY_CEXT", "0") == "1":
+ from sqlalchemy.util import has_compiled_ext
+
+ try:
+ has_compiled_ext(raise_=True)
+ except ImportError as err:
+ raise AssertionError(
+ "REQUIRE_SQLALCHEMY_CEXT is set but can't import the "
+ "cython extensions"
+ ) from err
+
+
@post
def _init_symbols(options, file_config):
from sqlalchemy.testing import config
return "%s, ..., %s" % (", ".join(res[0:3]), res[-1])
-def has_compiled_ext():
+def has_compiled_ext(raise_=False):
try:
from sqlalchemy.cyextension import collections # noqa F401
from sqlalchemy.cyextension import immutabledict # noqa F401
return True
except ImportError:
+ if raise_:
+ raise
return False
)
registry.map_imperatively(Child, child)
+ registry.configure()
+
yield Parent, Child
registry.dispose()
@testing.fixture(scope="function")
def stmt_fixture_one(self, mapping_fixture):
- # note that by using ORM elements we will have annotations in these
- # items also which is part of the performance hit
Parent, Child = mapping_fixture
return [
else:
current_key = key
- @profiling.function_call_count(variance=0.15, warmup=0)
- def test_statement_key_is_not_cached(self, stmt_fixture_one):
- current_key = None
- for stmt in stmt_fixture_one:
- key = stmt._generate_cache_key()
- assert key is not None
- if current_key:
- eq_(key, current_key)
- else:
- current_key = key
+ def test_statement_key_is_not_cached(
+ self, stmt_fixture_one, mapping_fixture
+ ):
+ Parent, Child = mapping_fixture
+
+ # run a totally different statement so that everything cache
+ # related not specific to the statement is warmed up
+ some_other_statement = (
+ select(Parent.id, Child.id)
+ .join_from(Parent, Child, Parent.children)
+ .where(Parent.id == 5)
+ )
+ some_other_statement._generate_cache_key()
+
+ @profiling.function_call_count(variance=0.15, warmup=0)
+ def go():
+ current_key = None
+ for stmt in stmt_fixture_one:
+ key = stmt._generate_cache_key()
+ assert key is not None
+ if current_key:
+ eq_(key, current_key)
+ else:
+ current_key = key
+
+ go()
self.row_long.y
+class CacheAnonMap(Case):
+ @staticmethod
+ def python():
+ from sqlalchemy.sql._py_util import cache_anon_map
+
+ return cache_anon_map
+
+ @staticmethod
+ def cython():
+ from sqlalchemy.cyextension.util import cache_anon_map
+
+ return cache_anon_map
+
+ IMPLEMENTATIONS = {"python": python.__func__, "cython": cython.__func__}
+
+ NUMBER = 1000000
+
+ def init_objects(self):
+ from sqlalchemy import column, bindparam
+
+ self.object_1 = column("x")
+ self.object_2 = bindparam("y")
+
+ self.impl_w_non_present = self.impl()
+ self.impl_w_present = iwp = self.impl()
+ iwp.get_anon(self.object_1)
+ iwp.get_anon(self.object_2)
+
+ @classmethod
+ def update_results(cls, results):
+ cls._divide_results(results, "cython", "python", "cy / py")
+
+ @test_case
+ def test_get_anon_non_present(self):
+ self.impl_w_non_present.get_anon(self.object_1)
+
+ @test_case
+ def test_get_anon_present(self):
+ self.impl_w_present.get_anon(self.object_1)
+
+ @test_case
+ def test_has_key_non_present(self):
+ id(self.object_1) in self.impl_w_non_present
+
+ @test_case
+ def test_has_key_present(self):
+ id(self.object_1) in self.impl_w_present
+
+
+class PrefixAnonMap(Case):
+ @staticmethod
+ def python():
+ from sqlalchemy.sql._py_util import prefix_anon_map
+
+ return prefix_anon_map
+
+ @staticmethod
+ def cython():
+ from sqlalchemy.cyextension.util import prefix_anon_map
+
+ return prefix_anon_map
+
+ IMPLEMENTATIONS = {"python": python.__func__, "cython": cython.__func__}
+
+ NUMBER = 1000000
+
+ def init_objects(self):
+ from sqlalchemy.sql.elements import _anonymous_label
+
+ self.name = _anonymous_label.safe_construct(58243, "some_column_name")
+
+ self.impl_w_non_present = self.impl()
+ self.impl_w_present = iwp = self.impl()
+ self.name.apply_map(iwp)
+
+ @classmethod
+ def update_results(cls, results):
+ cls._divide_results(results, "cython", "python", "cy / py")
+
+ @test_case
+ def test_apply_non_present(self):
+
+ self.name.apply_map(self.impl_w_non_present)
+
+ @test_case
+ def test_apply_present(self):
+
+ self.name.apply_map(self.impl_w_present)
+
+
def tabulate(results, inverse):
dim = 11
header = "{:<20}|" + (" {:<%s} |" % dim) * len(results)
# /home/classic/dev/sqlalchemy/test/profiles.txt
# This file is written out on a per-environment basis.
-# For each test in aaa_profiling, the corresponding function and
+# For each test in aaa_profiling, the corresponding function and
# environment is located within this file. If it doesn't exist,
# the test is skipped.
-# If a callcount does exist, it is compared to what we received.
+# If a callcount does exist, it is compared to what we received.
# assertions are raised if the counts do not match.
-#
-# To add a new callcount test, apply the function_call_count
-# decorator and re-run the tests using the --write-profiles
+#
+# To add a new callcount test, apply the function_call_count
+# decorator and re-run the tests using the --write-profiles
# option - this file will be rewritten including the new count.
-#
+#
# TEST: test.aaa_profiling.test_compiler.CompileTest.test_insert
# TEST: test.aaa_profiling.test_misc.CacheKeyTest.test_statement_key_is_not_cached
test.aaa_profiling.test_misc.CacheKeyTest.test_statement_key_is_not_cached x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_cextensions 5003
-test.aaa_profiling.test_misc.CacheKeyTest.test_statement_key_is_not_cached x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 5003
+test.aaa_profiling.test_misc.CacheKeyTest.test_statement_key_is_not_cached x86_64_linux_cpython_3.10_sqlite_pysqlite_dbapiunicode_nocextensions 6387
# TEST: test.aaa_profiling.test_misc.EnumTest.test_create_enum_from_pep_435_w_expensive_members
# wildcards OK). Need at least these
passenv=ORACLE_HOME NLS_LANG TOX_POSTGRESQL TOX_POSTGRESQL_PY2K TOX_MYSQL TOX_MYSQL_PY2K TOX_ORACLE TOX_MSSQL TOX_SQLITE TOX_SQLITE_FILE TOX_WORKERS EXTRA_SQLITE_DRIVERS EXTRA_PG_DRIVERS EXTRA_MYSQL_DRIVERS
-# for nocext, we rm *.so in lib in case we are doing usedevelop=True
commands=
- cext: /bin/true
+
+ # this line is only meaningful when usedevelop=True is enabled. we use
+ # that flag for coverage mode.
nocext: sh -c "rm -f lib/sqlalchemy/*.so"
+
{env:BASECOMMAND} {env:WORKERS} {env:SQLITE:} {env:EXTRA_SQLITE_DRIVERS:} {env:POSTGRESQL:} {env:EXTRA_PG_DRIVERS:} {env:MYSQL:} {env:EXTRA_MYSQL_DRIVERS:} {env:ORACLE:} {env:MSSQL:} {env:BACKENDONLY:} {env:IDENTS:} {env:MEMUSAGE:} {env:COVERAGE:} {posargs}
oracle,mssql,sqlite_file: python reap_dbs.py db_idents.txt