From: Mike Bayer Date: Mon, 6 Jan 2020 19:09:01 +0000 (-0500) Subject: Support GenericFunction.name passed as a quoted_name X-Git-Tag: rel_1_3_13~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=602351c4c3fd3d9ce66869866b7e21cfed1cf117;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git 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 (cherry picked from commit a697fcc1cb87b5a4e4f0c70361bd598086f4210f) --- diff --git a/doc/build/changelog/unreleased_13/5079.rst b/doc/build/changelog/unreleased_13/5079.rst new file mode 100644 index 0000000000..5b1a59791a --- /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 da527bbb16..e851ad9e77 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -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 diff --git a/lib/sqlalchemy/sql/functions.py b/lib/sqlalchemy/sql/functions.py index 6a26ced2af..85cc634c25 100644 --- a/lib/sqlalchemy/sql/functions.py +++ b/lib/sqlalchemy/sql/functions.py @@ -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 diff --git a/test/sql/test_functions.py b/test/sql/test_functions.py index 43dc51258b..0c60a25e54 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 @@ -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):