diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-01-06 14:09:01 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-01-06 14:11:18 -0500 |
| commit | a697fcc1cb87b5a4e4f0c70361bd598086f4210f (patch) | |
| tree | 7ca1b9fa64008d99ebde1d1170fb322b4bff68ea | |
| parent | d9dda8b94036dd5c76968f3ca0d3400f5d765894 (diff) | |
| download | sqlalchemy-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.rst | 18 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/functions.py | 30 | ||||
| -rw-r--r-- | test/sql/test_functions.py | 16 |
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): |
