Skip to content

Commit 557e94b

Browse files
authored
Merge pull request #179 from SectorLabs/181799346-postgis-compatibility
Support both psqlextra and PostGis backends at the same time
2 parents 0b133d1 + 384fafc commit 557e94b

File tree

5 files changed

+80
-47
lines changed

5 files changed

+80
-47
lines changed

psqlextra/backend/base.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,35 @@ class DatabaseWrapper(base_impl.backend()):
2222
introspection_class = PostgresIntrospection
2323
ops_class = PostgresOperations
2424

25+
def __init__(self, *args, **kwargs):
26+
super().__init__(*args, **kwargs)
27+
28+
# Some base back-ends such as the PostGIS back-end don't properly
29+
# set `ops_class` and `introspection_class` and initialize these
30+
# classes themselves.
31+
#
32+
# This can lead to broken functionality. We fix this automatically.
33+
34+
if not isinstance(self.introspection, self.introspection_class):
35+
self.introspection = self.introspection_class(self)
36+
37+
if not isinstance(self.ops, self.ops_class):
38+
self.ops = self.ops_class(self)
39+
40+
for expected_compiler_class in self.ops.compiler_classes:
41+
compiler_class = self.ops.compiler(expected_compiler_class.__name__)
42+
43+
if not issubclass(compiler_class, expected_compiler_class):
44+
logger.warning(
45+
"Compiler '%s.%s' is not properly deriving from '%s.%s'."
46+
% (
47+
compiler_class.__module__,
48+
compiler_class.__name__,
49+
expected_compiler_class.__module__,
50+
expected_compiler_class.__name__,
51+
)
52+
)
53+
2554
def prepare_database(self):
2655
"""Ran to prepare the configured database.
2756

psqlextra/backend/base_impl.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
from django.conf import settings
44
from django.core.exceptions import ImproperlyConfigured
5+
from django.db import DEFAULT_DB_ALIAS, connections
56

67
from django.db.backends.postgresql.base import ( # isort:skip
78
DatabaseWrapper as Psycopg2DatabaseWrapper,
89
)
910

1011

11-
def backend():
12-
"""Gets the base class for the custom database back-end.
12+
def base_backend_instance():
13+
"""Gets an instance of the base class for the custom database back-end.
1314
1415
This should be the Django PostgreSQL back-end. However,
1516
some people are already using a custom back-end from
@@ -19,6 +20,10 @@ def backend():
1920
As long as the specified base eventually also has
2021
the PostgreSQL back-end as a base, then everything should
2122
work as intended.
23+
24+
We create an instance to inspect what classes to subclass
25+
because not all back-ends set properties such as `ops_class`
26+
properly. The PostGIS back-end is a good example.
2227
"""
2328
base_class_name = getattr(
2429
settings,
@@ -49,7 +54,24 @@ def backend():
4954
% base_class_name
5055
)
5156

52-
return base_class
57+
base_instance = base_class(connections.databases[DEFAULT_DB_ALIAS])
58+
if base_instance.connection:
59+
raise ImproperlyConfigured(
60+
(
61+
"'%s' establishes a connection during initialization."
62+
" This is not expected and can lead to more connections"
63+
" being established than neccesarry."
64+
)
65+
% base_class_name
66+
)
67+
68+
return base_instance
69+
70+
71+
def backend():
72+
"""Gets the base class for the database back-end."""
73+
74+
return base_backend_instance().__class__
5375

5476

5577
def schema_editor():
@@ -59,7 +81,7 @@ def schema_editor():
5981
this.
6082
"""
6183

62-
return backend().SchemaEditorClass
84+
return base_backend_instance().SchemaEditorClass
6385

6486

6587
def introspection():
@@ -69,7 +91,7 @@ def introspection():
6991
for this.
7092
"""
7193

72-
return backend().introspection_class
94+
return base_backend_instance().introspection.__class__
7395

7496

7597
def operations():
@@ -79,4 +101,4 @@ def operations():
79101
this.
80102
"""
81103

82-
return backend().ops_class
104+
return base_backend_instance().ops.__class__

psqlextra/backend/operations.py

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from psqlextra.compiler import (
2-
PostgresAggregateCompiler,
3-
PostgresCompiler,
4-
PostgresDeleteCompiler,
5-
PostgresInsertCompiler,
6-
PostgresUpdateCompiler,
2+
SQLAggregateCompiler,
3+
SQLCompiler,
4+
SQLDeleteCompiler,
5+
SQLInsertCompiler,
6+
SQLUpdateCompiler,
77
)
88

99
from . import base_impl
@@ -12,25 +12,12 @@
1212
class PostgresOperations(base_impl.operations()):
1313
"""Simple operations specific to PostgreSQL."""
1414

15-
compiler_map = {
16-
"SQLCompiler": PostgresCompiler,
17-
"SQLInsertCompiler": PostgresInsertCompiler,
18-
"SQLUpdateCompiler": PostgresUpdateCompiler,
19-
"SQLDeleteCompiler": PostgresDeleteCompiler,
20-
"SQLAggregateCompiler": PostgresAggregateCompiler,
21-
}
15+
compiler_module = "psqlextra.compiler"
2216

23-
def __init__(self, *args, **kwargs):
24-
super().__init__(*args, **kwargs)
25-
26-
self._compiler_cache = None
27-
28-
def compiler(self, compiler_name: str):
29-
"""Gets the SQL compiler with the specified name."""
30-
31-
postgres_compiler = self.compiler_map.get(compiler_name)
32-
if postgres_compiler:
33-
return postgres_compiler
34-
35-
# Let Django try to find the compiler. Better run without caller comment than break
36-
return super().compiler(compiler_name)
17+
compiler_classes = [
18+
SQLCompiler,
19+
SQLDeleteCompiler,
20+
SQLAggregateCompiler,
21+
SQLUpdateCompiler,
22+
SQLInsertCompiler,
23+
]

psqlextra/compiler.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@
1111
from django.core.exceptions import SuspiciousOperation
1212
from django.db.models import Expression, Model, Q
1313
from django.db.models.fields.related import RelatedField
14-
from django.db.models.sql.compiler import (
15-
SQLAggregateCompiler,
16-
SQLCompiler,
17-
SQLDeleteCompiler,
18-
SQLInsertCompiler,
19-
SQLUpdateCompiler,
20-
)
14+
from django.db.models.sql import compiler as django_compiler
2115
from django.db.utils import ProgrammingError
2216

2317
from .expressions import HStoreValue
@@ -77,25 +71,25 @@ def append_caller_to_sql(sql):
7771
return sql
7872

7973

80-
class PostgresCompiler(SQLCompiler):
74+
class SQLCompiler(django_compiler.SQLCompiler):
8175
def as_sql(self, *args, **kwargs):
8276
sql, params = super().as_sql(*args, **kwargs)
8377
return append_caller_to_sql(sql), params
8478

8579

86-
class PostgresDeleteCompiler(SQLDeleteCompiler):
80+
class SQLDeleteCompiler(django_compiler.SQLDeleteCompiler):
8781
def as_sql(self, *args, **kwargs):
8882
sql, params = super().as_sql(*args, **kwargs)
8983
return append_caller_to_sql(sql), params
9084

9185

92-
class PostgresAggregateCompiler(SQLAggregateCompiler):
86+
class SQLAggregateCompiler(django_compiler.SQLAggregateCompiler):
9387
def as_sql(self, *args, **kwargs):
9488
sql, params = super().as_sql(*args, **kwargs)
9589
return append_caller_to_sql(sql), params
9690

9791

98-
class PostgresUpdateCompiler(SQLUpdateCompiler):
92+
class SQLUpdateCompiler(django_compiler.SQLUpdateCompiler):
9993
"""Compiler for SQL UPDATE statements that allows us to use expressions
10094
inside HStore values.
10195
@@ -152,7 +146,7 @@ def _does_dict_contain_expression(data: dict) -> bool:
152146
return False
153147

154148

155-
class PostgresInsertCompiler(SQLInsertCompiler):
149+
class SQLInsertCompiler(django_compiler.SQLInsertCompiler):
156150
"""Compiler for SQL INSERT statements."""
157151

158152
def as_sql(self, *args, **kwargs):
@@ -165,7 +159,7 @@ def as_sql(self, *args, **kwargs):
165159
return queries
166160

167161

168-
class PostgresInsertOnConflictCompiler(SQLInsertCompiler):
162+
class PostgresInsertOnConflictCompiler(django_compiler.SQLInsertCompiler):
169163
"""Compiler for SQL INSERT statements."""
170164

171165
def __init__(self, *args, **kwargs):
@@ -407,7 +401,7 @@ def _format_field_value(self, field_name) -> str:
407401
if isinstance(field, RelatedField) and isinstance(value, Model):
408402
value = value.pk
409403

410-
return SQLInsertCompiler.prepare_value(
404+
return django_compiler.SQLInsertCompiler.prepare_value(
411405
self,
412406
field,
413407
# Note: this deliberately doesn't use `pre_save_val` as we don't

psqlextra/sql.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from django.db.models import sql
99
from django.db.models.constants import LOOKUP_SEP
1010

11-
from .compiler import PostgresInsertOnConflictCompiler, PostgresUpdateCompiler
11+
from .compiler import PostgresInsertOnConflictCompiler
12+
from .compiler import SQLUpdateCompiler as PostgresUpdateCompiler
1213
from .expressions import HStoreColumn
1314
from .fields import HStoreField
1415
from .types import ConflictAction

0 commit comments

Comments
 (0)