summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/functions.py
diff options
context:
space:
mode:
authorAdrien Berchet <adrien.berchet@gmail.com>2019-04-15 13:59:18 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2019-04-29 17:24:32 -0400
commita10a4ea1248902349d789de7f5470bb8e437a584 (patch)
tree536acae15894ae8f60ac2aedd784e37575ccba39 /lib/sqlalchemy/sql/functions.py
parent64865304051b2af1ee4f90c6bf5e93378d4f302c (diff)
downloadsqlalchemy-a10a4ea1248902349d789de7f5470bb8e437a584.tar.gz
Add case insensitivity feature to GenericFunction.
The :class:`.GenericFunction` namespace is being migrated so that function names are looked up in a case-insensitive manner, as SQL functions do not collide on case sensitive differences nor is this something which would occur with user-defined functions or stored procedures. Lookups for functions declared with :class:`.GenericFunction` now use a case insensitive scheme, however a deprecation case is supported which allows two or more :class:`.GenericFunction` objects with the same name of different cases to exist, which will cause case sensitive lookups to occur for that particular name, while emitting a warning at function registration time. Thanks to Adrien Berchet for a lot of work on this complicated feature. Fixes: #4569 Closes: #4570 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/4570 Pull-request-sha: 37d4f3322b6bace88c99b959cb1916dbbc57610e Change-Id: Ief07c6eb55bf398f6aad85b60ef13ee6d1173109
Diffstat (limited to 'lib/sqlalchemy/sql/functions.py')
-rw-r--r--lib/sqlalchemy/sql/functions.py65
1 files changed, 63 insertions, 2 deletions
diff --git a/lib/sqlalchemy/sql/functions.py b/lib/sqlalchemy/sql/functions.py
index 0e92d5e50..d3775ea6b 100644
--- a/lib/sqlalchemy/sql/functions.py
+++ b/lib/sqlalchemy/sql/functions.py
@@ -37,6 +37,13 @@ from .. import util
_registry = util.defaultdict(dict)
+_case_sensitive_registry = util.defaultdict(
+ lambda: util.defaultdict(dict)
+)
+_CASE_SENSITIVE = util.symbol(
+ name="case_sensitive_function",
+ doc="Symbol to mark the functions that are switched into case-sensitive "
+ "mode.")
def register_function(identifier, fn, package="_default"):
@@ -49,7 +56,57 @@ def register_function(identifier, fn, package="_default"):
"""
reg = _registry[package]
- reg[identifier] = fn
+ case_sensitive_reg = _case_sensitive_registry[package]
+ raw_identifier = identifier
+ identifier = identifier.lower()
+
+ # Check if a function with the same lowercase identifier is registered.
+ if identifier in reg and reg[identifier] is not _CASE_SENSITIVE:
+ if raw_identifier in case_sensitive_reg[identifier]:
+ util.warn(
+ "The GenericFunction '{}' is already registered and "
+ "is going to be overriden.".format(identifier))
+ reg[identifier] = fn
+ else:
+ # If a function with the same lowercase identifier is registered,
+ # then these 2 functions are considered as case-sensitive.
+ # Note: This case should raise an error in a later release.
+ util.warn_deprecated(
+ "GenericFunction '{}' is already registered with "
+ "different letter case, so the previously registered function "
+ "'{}' is switched into case-sensitive mode. "
+ "GenericFunction objects will be fully case-insensitive in a "
+ "future release.".format(
+ raw_identifier,
+ list(case_sensitive_reg[identifier].keys())[0],
+ ))
+ reg[identifier] = _CASE_SENSITIVE
+
+ # Check if a function with different letter case identifier is registered.
+ elif identifier in case_sensitive_reg:
+ # Note: This case will be removed in a later release.
+ if (
+ raw_identifier not in case_sensitive_reg[identifier]
+ ):
+ util.warn_deprecated(
+ "GenericFunction(s) '{}' are already registered with "
+ "different letter cases and might interact with '{}'. "
+ "GenericFunction objects will be fully case-insensitive in a "
+ "future release.".format(
+ sorted(case_sensitive_reg[identifier].keys()),
+ raw_identifier))
+
+ else:
+ util.warn(
+ "The GenericFunction '{}' is already registered and "
+ "is going to be overriden.".format(raw_identifier))
+
+ # Register by default
+ else:
+ reg[identifier] = fn
+
+ # Always register in case-sensitive registry
+ case_sensitive_reg[identifier][raw_identifier] = fn
class FunctionElement(Executable, ColumnElement, FromClause):
@@ -453,7 +510,11 @@ class _FunctionGenerator(object):
package = None
if package is not None:
- func = _registry[package].get(fname)
+ func = _registry[package].get(fname.lower())
+ if func is _CASE_SENSITIVE:
+ case_sensitive_reg = _case_sensitive_registry[package]
+ func = case_sensitive_reg.get(fname.lower()).get(fname)
+
if func is not None:
return func(*c, **o)