]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Support GenericFunction.name passed as a quoted_name
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 6 Jan 2020 19:09:01 +0000 (14:09 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 6 Jan 2020 19:13:28 +0000 (14:13 -0500)
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
(cherry picked from commit a697fcc1cb87b5a4e4f0c70361bd598086f4210f)

doc/build/changelog/unreleased_13/5079.rst [new file with mode: 0644]
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/functions.py
test/sql/test_functions.py

diff --git a/doc/build/changelog/unreleased_13/5079.rst b/doc/build/changelog/unreleased_13/5079.rst
new file mode 100644 (file)
index 0000000..5b1a597
--- /dev/null
@@ -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`
+
index da527bbb16bccfa40fa33b5becdb7bb0a6793336..e851ad9e77da40f7bbd446c254c362236265dad6 100644 (file)
@@ -1121,6 +1121,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"
@@ -1129,6 +1130,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
index 6a26ced2af1cf2dc9e60828f0d04e90e72c25a77..85cc634c25c792755c2f7d4ce2034391aacc70c2 100644 (file)
@@ -57,7 +57,7 @@ def register_function(identifier, fn, package="_default"):
     reg = _registry[package]
     case_sensitive_reg = _case_sensitive_registry[package]
     raw_identifier = identifier
-    identifier = identifier.lower()
+    identifier = util.text_type(identifier).lower()
 
     # Check if a function with the same lowercase identifier is registered.
     if identifier in reg and reg[identifier] is not _CASE_SENSITIVE:
@@ -749,9 +749,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
index 43dc51258be7177d15614e9194b3fb41b75dfe13..0c60a25e5498a3b05c737325b417d3e70366cd78 100644 (file)
@@ -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
@@ -270,6 +271,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):