summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2020-01-06 14:09:01 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2020-01-06 14:11:18 -0500
commita697fcc1cb87b5a4e4f0c70361bd598086f4210f (patch)
tree7ca1b9fa64008d99ebde1d1170fb322b4bff68ea
parentd9dda8b94036dd5c76968f3ca0d3400f5d765894 (diff)
downloadsqlalchemy-a697fcc1cb87b5a4e4f0c70361bd598086f4210f.tar.gz
Support GenericFunction.name passed as a quoted_name
A function created using :class:`.GenericFunction` can now specify that the name of the function should be rendered with or without quotes by assigning the :class:`.quoted_name` construct to the .name element of the object. Prior to 1.3.4, quoting was never applied to function names, and some quoting was introduced in :ticket:`4467` but no means to force quoting for a mixed case name was available. Additionally, the :class:`.quoted_name` construct when used as the name will properly register its lowercase name in the function registry so that the name continues to be available via the ``func.`` registry. Fixes: #5079 Change-Id: I0653ab8b16e75e628ce82dbbc3d0f77f8336c407
-rw-r--r--doc/build/changelog/unreleased_13/5079.rst18
-rw-r--r--lib/sqlalchemy/sql/compiler.py2
-rw-r--r--lib/sqlalchemy/sql/functions.py30
-rw-r--r--test/sql/test_functions.py16
4 files changed, 63 insertions, 3 deletions
diff --git a/doc/build/changelog/unreleased_13/5079.rst b/doc/build/changelog/unreleased_13/5079.rst
new file mode 100644
index 000000000..5b1a59791
--- /dev/null
+++ b/doc/build/changelog/unreleased_13/5079.rst
@@ -0,0 +1,18 @@
+.. change::
+ :tags: usecase, sql
+ :tickets: 5079
+
+ A function created using :class:`.GenericFunction` can now specify that the
+ name of the function should be rendered with or without quotes by assigning
+ the :class:`.quoted_name` construct to the .name element of the object.
+ Prior to 1.3.4, quoting was never applied to function names, and some
+ quoting was introduced in :ticket:`4467` but no means to force quoting for
+ a mixed case name was available. Additionally, the :class:`.quoted_name`
+ construct when used as the name will properly register its lowercase name
+ in the function registry so that the name continues to be available via the
+ ``func.`` registry.
+
+ .. seealso::
+
+ :class:`.GenericFunction`
+
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 5333b1419..8499484f3 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -1329,6 +1329,7 @@ class SQLCompiler(Compiled):
name = (
self.preparer.quote(name)
if self.preparer._requires_quotes_illegal_chars(name)
+ or isinstance(name, elements.quoted_name)
else name
)
name = name + "%(expr)s"
@@ -1337,6 +1338,7 @@ class SQLCompiler(Compiled):
(
self.preparer.quote(tok)
if self.preparer._requires_quotes_illegal_chars(tok)
+ or isinstance(name, elements.quoted_name)
else tok
)
for tok in func.packagenames
diff --git a/lib/sqlalchemy/sql/functions.py b/lib/sqlalchemy/sql/functions.py
index 18fe42aa4..068fc6809 100644
--- a/lib/sqlalchemy/sql/functions.py
+++ b/lib/sqlalchemy/sql/functions.py
@@ -50,7 +50,8 @@ def register_function(identifier, fn, package="_default"):
"""
reg = _registry[package]
- identifier = identifier.lower()
+
+ identifier = util.text_type(identifier).lower()
# Check if a function with the same identifier is registered.
if identifier in reg:
@@ -544,7 +545,6 @@ class _FunctionGenerator(object):
if package is not None:
func = _registry[package].get(fname.lower())
-
if func is not None:
return func(*c, **o)
@@ -707,9 +707,33 @@ class GenericFunction(util.with_metaclass(_GenericMeta, Function)):
The above function will render as follows::
- >>> print func.geo.buffer()
+ >>> print(func.geo.buffer())
ST_Buffer()
+ The name will be rendered as is, however without quoting unless the name
+ contains special characters that require quoting. To force quoting
+ on or off for the name, use the :class:`.sqlalchemy.sql.quoted_name`
+ construct::
+
+ from sqlalchemy.sql import quoted_name
+
+ class GeoBuffer(GenericFunction):
+ type = Geometry
+ package = "geo"
+ name = quoted_name("ST_Buffer", True)
+ identifier = "buffer"
+
+ The above function will render as::
+
+ >>> print(func.geo.buffer())
+ "ST_Buffer"()
+
+ .. versionadded:: 1.3.13 The :class:`.quoted_name` construct is now
+ recognized for quoting when used with the "name" attribute of the
+ object, so that quoting can be forced on or off for the function
+ name.
+
+
"""
coerce_arguments = True
diff --git a/test/sql/test_functions.py b/test/sql/test_functions.py
index a46d1af54..6ee8a67b7 100644
--- a/test/sql/test_functions.py
+++ b/test/sql/test_functions.py
@@ -29,6 +29,7 @@ from sqlalchemy.dialects import postgresql
from sqlalchemy.dialects import sqlite
from sqlalchemy.sql import column
from sqlalchemy.sql import functions
+from sqlalchemy.sql import quoted_name
from sqlalchemy.sql import table
from sqlalchemy.sql.compiler import BIND_TEMPLATES
from sqlalchemy.sql.functions import FunctionElement
@@ -303,6 +304,21 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
assert isinstance(func.notmyfunc(), myfunc)
assert not isinstance(func.myfunc(), myfunc)
+ def test_custom_w_quoted_name(self):
+ class myfunc(GenericFunction):
+ name = quoted_name("NotMyFunc", quote=True)
+ identifier = "myfunc"
+
+ self.assert_compile(func.myfunc(), '"NotMyFunc"()')
+
+ def test_custom_w_quoted_name_no_identifier(self):
+ class myfunc(GenericFunction):
+ name = quoted_name("NotMyFunc", quote=True)
+
+ # note this requires that the quoted name be lower cased for
+ # correct lookup
+ self.assert_compile(func.notmyfunc(), '"NotMyFunc"()')
+
def test_custom_package_namespace(self):
def cls1(pk_name):
class myfunc(GenericFunction):