diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py index 1e957d105e..38af239cbe 100644 --- a/django/db/backends/base/features.py +++ b/django/db/backends/base/features.py @@ -415,6 +415,10 @@ class BaseDatabaseFeatures: # Does the Round() database function round to even? rounds_to_even = False + # Should dollar signs be prohibited in column aliases to prevent SQL + # injection? + prohibits_dollar_signs_in_column_aliases = False + # A set of dotted paths to tests in Django's test suite that are expected # to fail on this database. django_test_expected_failures = set() diff --git a/django/db/backends/postgresql/compiler.py b/django/db/backends/postgresql/compiler.py index 08d78e333a..48d0ccfd9d 100644 --- a/django/db/backends/postgresql/compiler.py +++ b/django/db/backends/postgresql/compiler.py @@ -1,6 +1,6 @@ from django.db.models.sql.compiler import ( # isort:skip SQLAggregateCompiler, - SQLCompiler as BaseSQLCompiler, + SQLCompiler, SQLDeleteCompiler, SQLInsertCompiler as BaseSQLInsertCompiler, SQLUpdateCompiler, @@ -25,15 +25,6 @@ class InsertUnnest(list): return "UNNEST(%s)" % ", ".join(self) -class SQLCompiler(BaseSQLCompiler): - def quote_name_unless_alias(self, name): - if "$" in name: - raise ValueError( - "Dollar signs are not permitted in column aliases on PostgreSQL." - ) - return super().quote_name_unless_alias(name) - - class SQLInsertCompiler(BaseSQLInsertCompiler): def assemble_as_sql(self, fields, value_rows): # Specialize bulk-insertion of literal values through UNNEST to diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py index 5e4ed320be..30d06c95c5 100644 --- a/django/db/backends/postgresql/features.py +++ b/django/db/backends/postgresql/features.py @@ -70,6 +70,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_nulls_distinct_unique_constraints = True supports_no_precision_decimalfield = True can_rename_index = True + prohibits_dollar_signs_in_column_aliases = True test_collations = { "deterministic": "C", "non_default": "sv-x-icu", diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 20f06ad168..9068d87a89 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -553,6 +553,14 @@ class SQLCompiler: for table names. This avoids problems with some SQL dialects that treat quoted strings specially (e.g. PostgreSQL). """ + if ( + self.connection.features.prohibits_dollar_signs_in_column_aliases + and "$" in name + ): + raise ValueError( + "Dollar signs are not permitted in column aliases on " + f"{self.connection.display_name}." + ) if name in self.quote_cache: return self.quote_cache[name] if ( diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py index 10cd05db63..6336cabafa 100644 --- a/tests/annotations/tests.py +++ b/tests/annotations/tests.py @@ -1545,8 +1545,11 @@ class AliasTests(TestCase): qs = Book.objects.alias( **{"crafted_alia$": FilteredRelation("authors")} ).values("name", "crafted_alia$") - if connection.vendor == "postgresql": - msg = "Dollar signs are not permitted in column aliases on PostgreSQL." + if connection.features.prohibits_dollar_signs_in_column_aliases: + msg = ( + "Dollar signs are not permitted in column aliases on " + f"{connection.display_name}." + ) with self.assertRaisesMessage(ValueError, msg): list(qs) else: