1 | import psycopg2
|
---|
2 |
|
---|
3 | from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
---|
4 | from django.conf import settings
|
---|
5 |
|
---|
6 | class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
---|
7 |
|
---|
8 | sql_alter_column_type = "ALTER COLUMN %(column)s TYPE %(type)s USING %(column)s::%(type)s"
|
---|
9 |
|
---|
10 | sql_create_sequence = "CREATE SEQUENCE %(sequence)s"
|
---|
11 | sql_delete_sequence = "DROP SEQUENCE IF EXISTS %(sequence)s CASCADE"
|
---|
12 | sql_set_sequence_max = "SELECT setval('%(sequence)s', MAX(%(column)s)) FROM %(table)s"
|
---|
13 |
|
---|
14 | sql_create_index = "CREATE INDEX %(name)s ON %(table)s%(using)s (%(columns)s)%(extra)s"
|
---|
15 | sql_create_varchar_index = "CREATE INDEX %(name)s ON %(table)s (%(columns)s varchar_pattern_ops)%(extra)s"
|
---|
16 | sql_create_text_index = "CREATE INDEX %(name)s ON %(table)s (%(columns)s text_pattern_ops)%(extra)s"
|
---|
17 | sql_delete_index = "DROP INDEX IF EXISTS %(name)s"
|
---|
18 |
|
---|
19 | # Setting the constraint to IMMEDIATE runs any deferred checks to allow
|
---|
20 | # dropping it in the same transaction.
|
---|
21 | sql_delete_fk = "SET CONSTRAINTS %(name)s IMMEDIATE; ALTER TABLE %(table)s DROP CONSTRAINT %(name)s"
|
---|
22 |
|
---|
23 | sql_delete_procedure = 'DROP FUNCTION %(procedure)s(%(param_types)s)'
|
---|
24 |
|
---|
25 | def quote_value(self, value):
|
---|
26 | return psycopg2.extensions.adapt(value)
|
---|
27 |
|
---|
28 | def _field_indexes_sql(self, model, field):
|
---|
29 | output = super()._field_indexes_sql(model, field)
|
---|
30 | like_index_statement = self._create_like_index_sql(model, field)
|
---|
31 | if like_index_statement is not None:
|
---|
32 | output.append(like_index_statement)
|
---|
33 | return output
|
---|
34 |
|
---|
35 | def _create_like_index_sql(self, model, field):
|
---|
36 | """
|
---|
37 | Return the statement to create an index with varchar operator pattern
|
---|
38 | when the column type is 'varchar' or 'text', otherwise return None.
|
---|
39 | """
|
---|
40 | db_type = field.db_type(connection=self.connection)
|
---|
41 | if db_type is not None and (field.db_index or field.unique):
|
---|
42 | # Fields with database column types of `varchar` and `text` need
|
---|
43 | # a second index that specifies their operator class, which is
|
---|
44 | # needed when performing correct LIKE queries outside the
|
---|
45 | # C locale. See #12234.
|
---|
46 | #
|
---|
47 | # The same doesn't apply to array fields such as varchar[size]
|
---|
48 | # and text[size], so skip them.
|
---|
49 | if '[' in db_type:
|
---|
50 | return None
|
---|
51 | if db_type.startswith('varchar'):
|
---|
52 | return self._create_index_sql(model, [field], suffix='_like', sql=self.sql_create_varchar_index)
|
---|
53 | elif db_type.startswith('text'):
|
---|
54 | return self._create_index_sql(model, [field], suffix='_like', sql=self.sql_create_text_index)
|
---|
55 | return None
|
---|
56 |
|
---|
57 | def _alter_column_type_sql(self, model, old_field, new_field, new_type):
|
---|
58 | """Make ALTER TYPE with SERIAL make sense."""
|
---|
59 | table = model._meta.db_table
|
---|
60 | if new_type.lower() in ("serial", "bigserial"):
|
---|
61 | column = new_field.column
|
---|
62 | sequence_name = "%s_%s_seq" % (table, column)
|
---|
63 | col_type = "integer" if new_type.lower() == "serial" else "bigint"
|
---|
64 | return (
|
---|
65 | (
|
---|
66 | self.sql_alter_column_type % {
|
---|
67 | "column": self.quote_name(column),
|
---|
68 | "type": col_type,
|
---|
69 | },
|
---|
70 | [],
|
---|
71 | ),
|
---|
72 | [
|
---|
73 | (
|
---|
74 | self.sql_delete_sequence % {
|
---|
75 | "sequence": self.quote_name(sequence_name),
|
---|
76 | },
|
---|
77 | [],
|
---|
78 | ),
|
---|
79 | (
|
---|
80 | self.sql_create_sequence % {
|
---|
81 | "sequence": self.quote_name(sequence_name),
|
---|
82 | },
|
---|
83 | [],
|
---|
84 | ),
|
---|
85 | (
|
---|
86 | # GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 5000 CACHE 1 ),
|
---|
87 | # DEFAULT nextval('catalog_data_federalstate_id_seq'::regclass) ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
|
---|
88 |
|
---|
89 | self.sql_alter_column % {
|
---|
90 | "table": self.quote_name(table),
|
---|
91 | "changes": self.sql_alter_column_default % {
|
---|
92 | "column": self.quote_name(column),
|
---|
93 | "default": "nextval('%s')" % self.quote_name(sequence_name),
|
---|
94 | }
|
---|
95 | },
|
---|
96 | [],
|
---|
97 | ),
|
---|
98 | (
|
---|
99 | self.sql_set_sequence_max % {
|
---|
100 | "table": self.quote_name(table),
|
---|
101 | "column": self.quote_name(column),
|
---|
102 | "sequence": self.quote_name(sequence_name),
|
---|
103 | },
|
---|
104 | [],
|
---|
105 | ),
|
---|
106 | ],
|
---|
107 | )
|
---|
108 | else:
|
---|
109 | return super()._alter_column_type_sql(model, old_field, new_field, new_type)
|
---|
110 |
|
---|
111 | def _alter_field(self, model, old_field, new_field, old_type, new_type,
|
---|
112 | old_db_params, new_db_params, strict=False):
|
---|
113 | # Drop indexes on varchar/text/citext columns that are changing to a
|
---|
114 | # different type.
|
---|
115 | if (old_field.db_index or old_field.unique) and (
|
---|
116 | (old_type.startswith('varchar') and not new_type.startswith('varchar')) or
|
---|
117 | (old_type.startswith('text') and not new_type.startswith('text')) or
|
---|
118 | (old_type.startswith('citext') and not new_type.startswith('citext'))
|
---|
119 | ):
|
---|
120 | index_name = self._create_index_name(model._meta.db_table, [old_field.column], suffix='_like')
|
---|
121 | self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_name))
|
---|
122 |
|
---|
123 | super()._alter_field(
|
---|
124 | model, old_field, new_field, old_type, new_type, old_db_params,
|
---|
125 | new_db_params, strict,
|
---|
126 | )
|
---|
127 | # Added an index? Create any PostgreSQL-specific indexes.
|
---|
128 | if ((not (old_field.db_index or old_field.unique) and new_field.db_index) or
|
---|
129 | (not old_field.unique and new_field.unique)):
|
---|
130 | like_index_statement = self._create_like_index_sql(model, new_field)
|
---|
131 | if like_index_statement is not None:
|
---|
132 | self.execute(like_index_statement)
|
---|
133 |
|
---|
134 | # Removed an index? Drop any PostgreSQL-specific indexes.
|
---|
135 | if old_field.unique and not (new_field.db_index or new_field.unique):
|
---|
136 | index_to_remove = self._create_index_name(model._meta.db_table, [old_field.column], suffix='_like')
|
---|
137 | self.execute(self._delete_constraint_sql(self.sql_delete_index, model, index_to_remove))
|
---|
138 |
|
---|
139 | def column_sql(self, model, field, include_default=False):
|
---|
140 | """
|
---|
141 | Take a field and return its column definition.
|
---|
142 | The field must already have had set_attributes_from_name() called.
|
---|
143 | """
|
---|
144 | # Get the column's type and use that as the basis of the SQL
|
---|
145 | db_params = field.db_parameters(connection=self.connection)
|
---|
146 | # Autoincrement SQL (for backends with post table definition variant)
|
---|
147 | if settings.DEBUG: print("=> " + field.get_internal_type(), end='')
|
---|
148 | sql = db_params['type']
|
---|
149 | if settings.DEBUG: print(" type: " + sql)
|
---|
150 | params = []
|
---|
151 | # Check for fields that aren't actually columns (e.g. M2M)
|
---|
152 | if sql is None:
|
---|
153 | return None, None
|
---|
154 | # Work out nullability
|
---|
155 | null = field.null
|
---|
156 | # If we were told to include a default value, do so
|
---|
157 | include_default = include_default and not self.skip_default(field)
|
---|
158 | if include_default:
|
---|
159 | default_value = self.effective_default(field)
|
---|
160 | if default_value is not None:
|
---|
161 | if self.connection.features.requires_literal_defaults:
|
---|
162 | # Some databases can't take defaults as a parameter (oracle)
|
---|
163 | # If this is the case, the individual schema backend should
|
---|
164 | # implement prepare_default
|
---|
165 | sql += " DEFAULT %s" % self.prepare_default(default_value)
|
---|
166 | else:
|
---|
167 | sql += " DEFAULT %s"
|
---|
168 | params += [default_value]
|
---|
169 | # Oracle treats the empty string ('') as null, so coerce the null
|
---|
170 | # option whenever '' is a possible value.
|
---|
171 | if (field.empty_strings_allowed and not field.primary_key and
|
---|
172 | self.connection.features.interprets_empty_strings_as_nulls):
|
---|
173 | null = True
|
---|
174 | if null and not self.connection.features.implied_column_null:
|
---|
175 | sql += " NULL"
|
---|
176 | elif not null:
|
---|
177 | sql += " NOT NULL"
|
---|
178 | # Primary key/unique outputs
|
---|
179 | if field.primary_key:
|
---|
180 | sql += " PRIMARY KEY"
|
---|
181 | if field.get_internal_type() in ("IdentityAutoField", "IdentityBigAutoField"):
|
---|
182 | sql += " GENERATED ALWAYS AS IDENTITY"
|
---|
183 | elif field.unique:
|
---|
184 | sql += " UNIQUE"
|
---|
185 | # Optionally add the tablespace if it's an implicitly indexed column
|
---|
186 | tablespace = field.db_tablespace or model._meta.db_tablespace
|
---|
187 | if tablespace and self.connection.features.supports_tablespaces and field.unique:
|
---|
188 | sql += " %s" % self.connection.ops.tablespace_sql(tablespace, inline=True)
|
---|
189 | # Return the sql
|
---|
190 | return sql, params
|
---|
191 |
|
---|
192 | def create_model(self, model):
|
---|
193 | """
|
---|
194 | Create a table and any accompanying indexes or unique constraints for
|
---|
195 | the given `model`.
|
---|
196 | """
|
---|
197 | # Create column SQL, add FK deferreds if needed
|
---|
198 | column_sqls = []
|
---|
199 | params = []
|
---|
200 | if settings.DEBUG: print('')
|
---|
201 | for field in model._meta.local_fields:
|
---|
202 | # SQL
|
---|
203 | definition, extra_params = self.column_sql(model, field)
|
---|
204 | if definition is None:
|
---|
205 | continue
|
---|
206 | # Check constraints can go on the column SQL here
|
---|
207 | db_params = field.db_parameters(connection=self.connection)
|
---|
208 | if db_params['check']:
|
---|
209 | definition += " CHECK (%s)" % db_params['check']
|
---|
210 | # Autoincrement SQL (for backends with inline variant)
|
---|
211 | col_type_suffix = field.db_type_suffix(connection=self.connection)
|
---|
212 | if col_type_suffix:
|
---|
213 | definition += " %s" % col_type_suffix
|
---|
214 | params.extend(extra_params)
|
---|
215 | # FK
|
---|
216 | if field.remote_field and field.db_constraint:
|
---|
217 | to_table = field.remote_field.model._meta.db_table
|
---|
218 | to_column = field.remote_field.model._meta.get_field(field.remote_field.field_name).column
|
---|
219 | if self.sql_create_inline_fk:
|
---|
220 | definition += " " + self.sql_create_inline_fk % {
|
---|
221 | "to_table": self.quote_name(to_table),
|
---|
222 | "to_column": self.quote_name(to_column),
|
---|
223 | }
|
---|
224 | elif self.connection.features.supports_foreign_keys:
|
---|
225 | self.deferred_sql.append(self._create_fk_sql(model, field, "_fk_%(to_table)s_%(to_column)s"))
|
---|
226 | # Add the SQL to our big list
|
---|
227 | column_sqls.append("%s %s" % (
|
---|
228 | self.quote_name(field.column),
|
---|
229 | definition,
|
---|
230 | ))
|
---|
231 | # Autoincrement SQL (for backends with post table definition variant)
|
---|
232 | if field.get_internal_type() in ("AutoField", "BigAutoField"):
|
---|
233 | autoinc_sql = self.connection.ops.autoinc_sql(model._meta.db_table, field.column)
|
---|
234 | if autoinc_sql:
|
---|
235 | self.deferred_sql.extend(autoinc_sql)
|
---|
236 |
|
---|
237 | # Add any unique_togethers (always deferred, as some fields might be
|
---|
238 | # created afterwards, like geometry fields with some backends)
|
---|
239 | for fields in model._meta.unique_together:
|
---|
240 | columns = [model._meta.get_field(field).column for field in fields]
|
---|
241 | self.deferred_sql.append(self._create_unique_sql(model, columns))
|
---|
242 | # Make the table
|
---|
243 | sql = self.sql_create_table % {
|
---|
244 | "table": self.quote_name(model._meta.db_table),
|
---|
245 | "definition": ", ".join(column_sqls)
|
---|
246 | }
|
---|
247 | if model._meta.db_tablespace:
|
---|
248 | tablespace_sql = self.connection.ops.tablespace_sql(model._meta.db_tablespace)
|
---|
249 | if tablespace_sql:
|
---|
250 | sql += ' ' + tablespace_sql
|
---|
251 | # Prevent using [] as params, in the case a literal '%' is used in the definition
|
---|
252 | self.execute(sql, params or None)
|
---|
253 | if settings.DEBUG: print("=> "+ str(sql))
|
---|
254 |
|
---|
255 | # Add any field index and index_together's (deferred as SQLite3 _remake_table needs it)
|
---|
256 | self.deferred_sql.extend(self._model_indexes_sql(model))
|
---|
257 |
|
---|
258 | # Make M2M tables
|
---|
259 | for field in model._meta.local_many_to_many:
|
---|
260 | if field.remote_field.through._meta.auto_created:
|
---|
261 | self.create_model(field.remote_field.through)
|
---|