django-migrate-sql-deux 0.5.2__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. django_migrate_sql_deux-0.7.0/PKG-INFO +317 -0
  2. django_migrate_sql_deux-0.7.0/README.md +276 -0
  3. django_migrate_sql_deux-0.7.0/django_migrate_sql_deux.egg-info/PKG-INFO +317 -0
  4. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/django_migrate_sql_deux.egg-info/SOURCES.txt +2 -1
  5. django_migrate_sql_deux-0.7.0/django_migrate_sql_deux.egg-info/requires.txt +1 -0
  6. django_migrate_sql_deux-0.7.0/migrate_sql/__init__.py +1 -0
  7. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/migrate_sql/autodetector.py +55 -5
  8. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/migrate_sql/management/commands/makemigrations.py +8 -1
  9. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/migrate_sql/operations.py +27 -0
  10. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/setup.py +19 -10
  11. django_migrate_sql_deux-0.7.0/tests/test_app/urls.py +6 -0
  12. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_project/settings.py +1 -0
  13. django_migrate_sql_deux-0.7.0/tests/test_project/urls.py +6 -0
  14. django_migrate_sql_deux-0.5.2/PKG-INFO +0 -302
  15. django_migrate_sql_deux-0.5.2/README.rst +0 -274
  16. django_migrate_sql_deux-0.5.2/django_migrate_sql_deux.egg-info/PKG-INFO +0 -302
  17. django_migrate_sql_deux-0.5.2/migrate_sql/__init__.py +0 -1
  18. django_migrate_sql_deux-0.5.2/tests/test_app/urls.py +0 -13
  19. django_migrate_sql_deux-0.5.2/tests/test_project/urls.py +0 -10
  20. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/LICENSE +0 -0
  21. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/django_migrate_sql_deux.egg-info/dependency_links.txt +0 -0
  22. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/django_migrate_sql_deux.egg-info/not-zip-safe +0 -0
  23. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/django_migrate_sql_deux.egg-info/top_level.txt +0 -0
  24. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/migrate_sql/config.py +0 -0
  25. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/migrate_sql/graph.py +0 -0
  26. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/migrate_sql/management/__init__.py +0 -0
  27. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/migrate_sql/management/commands/__init__.py +0 -0
  28. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/pyproject.toml +0 -0
  29. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/setup.cfg +0 -0
  30. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/__init__.py +0 -0
  31. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/manage.py +0 -0
  32. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/__init__.py +0 -0
  33. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/apps.py +0 -0
  34. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations/0001_initial.py +0 -0
  35. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations/__init__.py +0 -0
  36. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_change/0001_initial.py +0 -0
  37. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_change/0002_auto_20151224_1827.py +0 -0
  38. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_change/__init__.py +0 -0
  39. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_deps_delete/0001_initial.py +0 -0
  40. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_deps_delete/0002_auto_20160106_0947.py +0 -0
  41. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_deps_delete/0003_auto_20160108_0048.py +0 -0
  42. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_deps_delete/0004_auto_20160108_0048.py +0 -0
  43. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_deps_delete/__init__.py +0 -0
  44. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_deps_update/0001_initial.py +0 -0
  45. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_deps_update/0002_auto_20160106_0947.py +0 -0
  46. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_deps_update/__init__.py +0 -0
  47. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_recreate/0001_initial.py +0 -0
  48. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_recreate/0002_auto_20151224_1827.py +0 -0
  49. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_recreate/0003_auto_20160106_1038.py +0 -0
  50. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/migrations_recreate/__init__.py +0 -0
  51. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/models.py +0 -0
  52. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/sql_config.py +0 -0
  53. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/test_migrations.py +0 -0
  54. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app/test_utils.py +0 -0
  55. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app2/__init__.py +0 -0
  56. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app2/apps.py +0 -0
  57. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app2/migrations/__init__.py +0 -0
  58. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app2/migrations_deps_delete/0001_initial.py +0 -0
  59. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app2/migrations_deps_delete/0002_auto_20160108_0041.py +0 -0
  60. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app2/migrations_deps_delete/__init__.py +0 -0
  61. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app2/migrations_deps_update/0001_initial.py +0 -0
  62. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app2/migrations_deps_update/__init__.py +0 -0
  63. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app2/models.py +0 -0
  64. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app2/sql_config.py +0 -0
  65. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_app2/urls.py +0 -0
  66. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_project/__init__.py +0 -0
  67. {django_migrate_sql_deux-0.5.2 → django_migrate_sql_deux-0.7.0}/tests/test_project/wsgi.py +0 -0
@@ -0,0 +1,317 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-migrate-sql-deux
3
+ Version: 0.7.0
4
+ Summary: Migration support for raw SQL in Django
5
+ Home-page: https://github.com/browniebroke/django-migrate-sql-deux
6
+ Author: Bruno Alla
7
+ Author-email: oss@browniebroke.com
8
+ License: BSD
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Framework :: Django
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: BSD License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Framework :: Django :: 3.2
20
+ Classifier: Framework :: Django :: 4.0
21
+ Classifier: Framework :: Django :: 4.1
22
+ Classifier: Framework :: Django :: 4.2
23
+ Classifier: Framework :: Django :: 5.0
24
+ Classifier: Framework :: Django :: 5.1
25
+ Classifier: Framework :: Django :: 5.2
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: Django>=3.2
30
+ Dynamic: author
31
+ Dynamic: author-email
32
+ Dynamic: classifier
33
+ Dynamic: description
34
+ Dynamic: description-content-type
35
+ Dynamic: home-page
36
+ Dynamic: license
37
+ Dynamic: license-file
38
+ Dynamic: requires-dist
39
+ Dynamic: requires-python
40
+ Dynamic: summary
41
+
42
+ # django-migrate-sql-deux
43
+
44
+ <p align="center">
45
+ <a href="https://github.com/browniebroke/django-migrate-sql-deux/actions/workflows/ci.yml?query=branch%3Amain">
46
+ <img src="https://img.shields.io/github/actions/workflow/status/browniebroke/django-migrate-sql-deux/ci.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status" >
47
+ </a>
48
+ <a href="https://codecov.io/gh/browniebroke/django-migrate-sql-deux">
49
+ <img src="https://img.shields.io/codecov/c/github/browniebroke/django-migrate-sql-deux.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">
50
+ </a>
51
+ </p>
52
+ <p align="center">
53
+ <a href="https://github.com/astral-sh/uv">
54
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json" alt="uv">
55
+ </a>
56
+ <a href="https://github.com/astral-sh/ruff">
57
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
58
+ </a>
59
+ <a href="https://github.com/pre-commit/pre-commit">
60
+ <img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">
61
+ </a>
62
+ </p>
63
+ <p align="center">
64
+ <a href="https://pypi.org/project/django-migrate-sql-deux/">
65
+ <img src="https://img.shields.io/pypi/v/django-migrate-sql-deux.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
66
+ </a>
67
+ <img src="https://img.shields.io/pypi/pyversions/django-migrate-sql-deux.svg?style=flat-square&logo=python&amp;logoColor=fff" alt="Supported Python versions">
68
+ <img src="https://img.shields.io/pypi/l/django-migrate-sql-deux.svg?style=flat-square" alt="License">
69
+ </p>
70
+
71
+ ---
72
+
73
+ **Source Code**: <a href="https://github.com/browniebroke/django-migrate-sql-deux" target="_blank">https://github.com/browniebroke/django-migrate-sql-deux </a>
74
+
75
+ ---
76
+
77
+ Django Migrations support for raw SQL.
78
+
79
+ > [!NOTE]
80
+ > This package is a fork of the `django-migrate-sql` package, originally published by Bogdan Klichuk. This package appears unmaintained, so we decided to start a fork as we depended on it. Most of the code is from the original author.
81
+
82
+ ## About
83
+
84
+ This tool implements mechanism for managing changes to custom SQL entities (functions, types, indices, triggers) using built-in migration mechanism. Technically creates a sophistication layer on top of the
85
+ `RunSQL` Django operation.
86
+
87
+ ## What it does
88
+
89
+ - Makes maintaining your SQL functions, custom composite types, indices and triggers easier.
90
+ - Structures SQL into configuration of **SQL items**, that are
91
+ identified by names and divided among apps, just like models.
92
+ - Automatically gathers and persists changes of your custom SQL into
93
+ migrations using `makemigrations`.
94
+ - Properly executes backwards/forwards keeping integrity of database.
95
+ - Create -> Drop -> Recreate approach for changes to items that do not
96
+ support altering and require dropping and recreating.
97
+ - Dependencies system for SQL items, which solves the problem of
98
+ updating items, that rely on others (for example custom
99
+ types/functions that use other custom types), and require dropping all
100
+ dependency tree previously with further recreation.
101
+
102
+ ## What it does not
103
+
104
+ - Does not parse SQL nor validate queries during `makemigrations` or
105
+ `migrate` because is database-agnostic. For this same reason setting
106
+ up proper dependencies is user's responsibility.
107
+ - Does not create `ALTER` queries for items that support this, for
108
+ example `ALTER TYPE` in PostgreSQL, because is database-agnostic. In
109
+ case your tools allow rolling all the changes through `ALTER` queries,
110
+ you can consider not using this app **or** restructure migrations
111
+ manually after creation by nesting generated operations into
112
+ `` `state_operations `` of [`RunSQL`](https://docs.djangoproject.com/en/1.8/ref/migration-operations/#runsql)
113
+ that does `ALTER`.
114
+ - (**TODO**)During `migrate` does not restore full state of items for
115
+ analysis, thus does not notify about existing changes to schema that
116
+ are not migrated **nor** does not recognize circular dependencies
117
+ during migration execution.
118
+
119
+ ## Installation
120
+
121
+ Install from PyPi:
122
+
123
+ ```shell
124
+ pip install django-migrate-sql-deux
125
+ ```
126
+
127
+ Add `migrate_sql` to `INSTALLED_APPS`:
128
+
129
+ ```python
130
+ INSTALLED_APPS = [
131
+ # ...
132
+ 'migrate_sql',
133
+ ]
134
+ ```
135
+
136
+ App defines a custom `makemigrations` command, that inherits from
137
+ Django's core one, so in order `migrate_sql` app to kick in put it
138
+ after any other apps that redefine `makemigrations` command too.
139
+
140
+ ## Usage
141
+
142
+ 1) Create `sql_config.py` module to root of a target app you want to
143
+ manage custom SQL for.
144
+ 2) Define SQL items in it (`sql_items`), for example:
145
+
146
+ ```python
147
+ # PostgreSQL example.
148
+ # Let's define a simple function and let `migrate_sql` manage it's changes.
149
+
150
+ from migrate_sql.config import SQLItem
151
+
152
+ sql_items = [
153
+ SQLItem(
154
+ 'make_sum', # name of the item
155
+ 'create or replace function make_sum(a int, b int) returns int as $$ '
156
+ 'begin return a + b; end; '
157
+ '$$ language plpgsql;', # forward sql
158
+ reverse_sql='drop function make_sum(int, int);', # sql for removal
159
+ ),
160
+ ]
161
+ ```
162
+
163
+ 3) Create migration `./manage.py makemigrations`:
164
+
165
+ Migrations for 'app_name':
166
+ 0002_auto_xxxx.py:
167
+ - Create SQL "make_sum"
168
+
169
+ You can take a look at content this generated:
170
+
171
+ ```python
172
+ from django.db import migrations, models
173
+ import migrate_sql.operations
174
+
175
+
176
+ class Migration(migrations.Migration):
177
+ dependencies = [
178
+ ('app_name', '0001_initial'),
179
+ ]
180
+ operations = [
181
+ migrate_sql.operations.CreateSQL(
182
+ name='make_sum',
183
+ sql='create or replace function make_sum(a int, b int) returns int as $$ begin return a + b; end; $$ language plpgsql;',
184
+ reverse_sql='drop function make_sum(int, int);',
185
+ ),
186
+ ]
187
+ ```
188
+
189
+ 4) Execute migration `./manage.py migrate`:
190
+
191
+ Operations to perform:
192
+ Apply all migrations: app_name
193
+ Running migrations:
194
+ Rendering model states... DONE
195
+ Applying app_name.0002_xxxx... OK
196
+
197
+ Check result in `./manage.py dbshell`:
198
+
199
+ db_name=# select make_sum(12, 15);
200
+ make_sum
201
+ ----------
202
+ 27
203
+ (1 row)
204
+
205
+ Now, say, you want to change the function implementation so that it
206
+ takes a custom type as argument:
207
+
208
+ 5) Edit your `sql_config.py`:
209
+
210
+ ``` python
211
+ # PostgreSQL example #2.
212
+ # Function and custom type.
213
+
214
+ from migrate_sql.config import SQLItem
215
+
216
+ sql_items = [
217
+ SQLItem(
218
+ 'make_sum', # name of the item
219
+ 'create or replace function make_sum(a mynum, b mynum) returns mynum as $$ '
220
+ 'begin return (a.num + b.num, 'result')::mynum; end; '
221
+ '$$ language plpgsql;', # forward sql
222
+ reverse_sql='drop function make_sum(mynum, mynum);', # sql for removal
223
+ # depends on `mynum` since takes it as argument. we won't be able to drop function
224
+ # without dropping `mynum` first.
225
+ dependencies=[('app_name', 'mynum')],
226
+ ),
227
+ SQLItem(
228
+ 'mynum' # name of the item
229
+ 'create type mynum as (num int, name varchar(20));', # forward sql
230
+ reverse_sql='drop type mynum;', # sql for removal
231
+ ),
232
+ ]
233
+ ```
234
+
235
+ 6) Generate migration `./manage.py makemigrations`:
236
+
237
+ ```
238
+ Migrations for 'app_name':
239
+ 0003_xxxx:
240
+ - Reverse alter SQL "make_sum"
241
+ - Create SQL "mynum"
242
+ - Alter SQL "make_sum"
243
+ - Alter SQL state "make_sum"
244
+ ```
245
+
246
+ You can take a look at the content this generated:
247
+
248
+ ```python
249
+ from django.db import migrations, models
250
+ import migrate_sql.operations
251
+
252
+
253
+ class Migration(migrations.Migration):
254
+ dependencies = [
255
+ ('app_name', '0002_xxxx'),
256
+ ]
257
+ operations = [
258
+ migrate_sql.operations.ReverseAlterSQL(
259
+ name='make_sum',
260
+ sql='drop function make_sum(int, int);',
261
+ reverse_sql='create or replace function make_sum(a int, b int) returns int as $$ begin return a + b; end; $$ language plpgsql;',
262
+ ),
263
+ migrate_sql.operations.CreateSQL(
264
+ name='mynum',
265
+ sql='create type mynum as (num int, name varchar(20));',
266
+ reverse_sql='drop type mynum;',
267
+ ),
268
+ migrate_sql.operations.AlterSQL(
269
+ name='make_sum',
270
+ sql='create or replace function make_sum(a mynum, b mynum) returns mynum as $$ begin return (a.num + b.num, \'result\')::mynum; end; $$ language plpgsql;',
271
+ reverse_sql='drop function make_sum(mynum, mynum);',
272
+ ),
273
+ migrate_sql.operations.AlterSQLState(
274
+ name='make_sum',
275
+ add_dependencies=(('app_name', 'mynum'),),
276
+ ),
277
+ ]
278
+ ```
279
+
280
+ **NOTE:** Previous function is completely dropped before creation
281
+ because definition of it changed. `CREATE OR REPLACE` would create
282
+ another version of it, so `DROP` makes it clean.
283
+
284
+ **If you put `replace=True` as kwarg to an `SQLItem`
285
+ definition, it will NOT drop + create it, but just rerun forward SQL,
286
+ which is `CREATE OR REPLACE` in this example.**
287
+
288
+ 7) Execute migration `./manage.py migrate`:
289
+
290
+
291
+ ```
292
+ Operations to perform:
293
+ Apply all migrations: app_name
294
+ Running migrations:
295
+ Rendering model states... DONE
296
+ Applying brands.0003_xxxx... OK
297
+ ```
298
+
299
+ Check results:
300
+
301
+ ```
302
+ db_name=# select make_sum((5, 'a')::mynum, (3, 'b')::mynum);
303
+ make_sum
304
+ ------------
305
+ (8,result)
306
+ (1 row)
307
+
308
+ db_name=# select make_sum(12, 15);
309
+ ERROR: function make_sum(integer, integer) does not exist
310
+ LINE 1: select make_sum(12, 15);
311
+ ^
312
+ HINT: No function matches the given name and argument types. You might need to add explicit type casts.
313
+ ```
314
+
315
+ For more examples see `tests`.
316
+
317
+ Feel free to [open new issues](https://github.com/browniebroke/django-migrate-sql-deux/issues).
@@ -0,0 +1,276 @@
1
+ # django-migrate-sql-deux
2
+
3
+ <p align="center">
4
+ <a href="https://github.com/browniebroke/django-migrate-sql-deux/actions/workflows/ci.yml?query=branch%3Amain">
5
+ <img src="https://img.shields.io/github/actions/workflow/status/browniebroke/django-migrate-sql-deux/ci.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status" >
6
+ </a>
7
+ <a href="https://codecov.io/gh/browniebroke/django-migrate-sql-deux">
8
+ <img src="https://img.shields.io/codecov/c/github/browniebroke/django-migrate-sql-deux.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">
9
+ </a>
10
+ </p>
11
+ <p align="center">
12
+ <a href="https://github.com/astral-sh/uv">
13
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json" alt="uv">
14
+ </a>
15
+ <a href="https://github.com/astral-sh/ruff">
16
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
17
+ </a>
18
+ <a href="https://github.com/pre-commit/pre-commit">
19
+ <img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">
20
+ </a>
21
+ </p>
22
+ <p align="center">
23
+ <a href="https://pypi.org/project/django-migrate-sql-deux/">
24
+ <img src="https://img.shields.io/pypi/v/django-migrate-sql-deux.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
25
+ </a>
26
+ <img src="https://img.shields.io/pypi/pyversions/django-migrate-sql-deux.svg?style=flat-square&logo=python&amp;logoColor=fff" alt="Supported Python versions">
27
+ <img src="https://img.shields.io/pypi/l/django-migrate-sql-deux.svg?style=flat-square" alt="License">
28
+ </p>
29
+
30
+ ---
31
+
32
+ **Source Code**: <a href="https://github.com/browniebroke/django-migrate-sql-deux" target="_blank">https://github.com/browniebroke/django-migrate-sql-deux </a>
33
+
34
+ ---
35
+
36
+ Django Migrations support for raw SQL.
37
+
38
+ > [!NOTE]
39
+ > This package is a fork of the `django-migrate-sql` package, originally published by Bogdan Klichuk. This package appears unmaintained, so we decided to start a fork as we depended on it. Most of the code is from the original author.
40
+
41
+ ## About
42
+
43
+ This tool implements mechanism for managing changes to custom SQL entities (functions, types, indices, triggers) using built-in migration mechanism. Technically creates a sophistication layer on top of the
44
+ `RunSQL` Django operation.
45
+
46
+ ## What it does
47
+
48
+ - Makes maintaining your SQL functions, custom composite types, indices and triggers easier.
49
+ - Structures SQL into configuration of **SQL items**, that are
50
+ identified by names and divided among apps, just like models.
51
+ - Automatically gathers and persists changes of your custom SQL into
52
+ migrations using `makemigrations`.
53
+ - Properly executes backwards/forwards keeping integrity of database.
54
+ - Create -> Drop -> Recreate approach for changes to items that do not
55
+ support altering and require dropping and recreating.
56
+ - Dependencies system for SQL items, which solves the problem of
57
+ updating items, that rely on others (for example custom
58
+ types/functions that use other custom types), and require dropping all
59
+ dependency tree previously with further recreation.
60
+
61
+ ## What it does not
62
+
63
+ - Does not parse SQL nor validate queries during `makemigrations` or
64
+ `migrate` because is database-agnostic. For this same reason setting
65
+ up proper dependencies is user's responsibility.
66
+ - Does not create `ALTER` queries for items that support this, for
67
+ example `ALTER TYPE` in PostgreSQL, because is database-agnostic. In
68
+ case your tools allow rolling all the changes through `ALTER` queries,
69
+ you can consider not using this app **or** restructure migrations
70
+ manually after creation by nesting generated operations into
71
+ `` `state_operations `` of [`RunSQL`](https://docs.djangoproject.com/en/1.8/ref/migration-operations/#runsql)
72
+ that does `ALTER`.
73
+ - (**TODO**)During `migrate` does not restore full state of items for
74
+ analysis, thus does not notify about existing changes to schema that
75
+ are not migrated **nor** does not recognize circular dependencies
76
+ during migration execution.
77
+
78
+ ## Installation
79
+
80
+ Install from PyPi:
81
+
82
+ ```shell
83
+ pip install django-migrate-sql-deux
84
+ ```
85
+
86
+ Add `migrate_sql` to `INSTALLED_APPS`:
87
+
88
+ ```python
89
+ INSTALLED_APPS = [
90
+ # ...
91
+ 'migrate_sql',
92
+ ]
93
+ ```
94
+
95
+ App defines a custom `makemigrations` command, that inherits from
96
+ Django's core one, so in order `migrate_sql` app to kick in put it
97
+ after any other apps that redefine `makemigrations` command too.
98
+
99
+ ## Usage
100
+
101
+ 1) Create `sql_config.py` module to root of a target app you want to
102
+ manage custom SQL for.
103
+ 2) Define SQL items in it (`sql_items`), for example:
104
+
105
+ ```python
106
+ # PostgreSQL example.
107
+ # Let's define a simple function and let `migrate_sql` manage it's changes.
108
+
109
+ from migrate_sql.config import SQLItem
110
+
111
+ sql_items = [
112
+ SQLItem(
113
+ 'make_sum', # name of the item
114
+ 'create or replace function make_sum(a int, b int) returns int as $$ '
115
+ 'begin return a + b; end; '
116
+ '$$ language plpgsql;', # forward sql
117
+ reverse_sql='drop function make_sum(int, int);', # sql for removal
118
+ ),
119
+ ]
120
+ ```
121
+
122
+ 3) Create migration `./manage.py makemigrations`:
123
+
124
+ Migrations for 'app_name':
125
+ 0002_auto_xxxx.py:
126
+ - Create SQL "make_sum"
127
+
128
+ You can take a look at content this generated:
129
+
130
+ ```python
131
+ from django.db import migrations, models
132
+ import migrate_sql.operations
133
+
134
+
135
+ class Migration(migrations.Migration):
136
+ dependencies = [
137
+ ('app_name', '0001_initial'),
138
+ ]
139
+ operations = [
140
+ migrate_sql.operations.CreateSQL(
141
+ name='make_sum',
142
+ sql='create or replace function make_sum(a int, b int) returns int as $$ begin return a + b; end; $$ language plpgsql;',
143
+ reverse_sql='drop function make_sum(int, int);',
144
+ ),
145
+ ]
146
+ ```
147
+
148
+ 4) Execute migration `./manage.py migrate`:
149
+
150
+ Operations to perform:
151
+ Apply all migrations: app_name
152
+ Running migrations:
153
+ Rendering model states... DONE
154
+ Applying app_name.0002_xxxx... OK
155
+
156
+ Check result in `./manage.py dbshell`:
157
+
158
+ db_name=# select make_sum(12, 15);
159
+ make_sum
160
+ ----------
161
+ 27
162
+ (1 row)
163
+
164
+ Now, say, you want to change the function implementation so that it
165
+ takes a custom type as argument:
166
+
167
+ 5) Edit your `sql_config.py`:
168
+
169
+ ``` python
170
+ # PostgreSQL example #2.
171
+ # Function and custom type.
172
+
173
+ from migrate_sql.config import SQLItem
174
+
175
+ sql_items = [
176
+ SQLItem(
177
+ 'make_sum', # name of the item
178
+ 'create or replace function make_sum(a mynum, b mynum) returns mynum as $$ '
179
+ 'begin return (a.num + b.num, 'result')::mynum; end; '
180
+ '$$ language plpgsql;', # forward sql
181
+ reverse_sql='drop function make_sum(mynum, mynum);', # sql for removal
182
+ # depends on `mynum` since takes it as argument. we won't be able to drop function
183
+ # without dropping `mynum` first.
184
+ dependencies=[('app_name', 'mynum')],
185
+ ),
186
+ SQLItem(
187
+ 'mynum' # name of the item
188
+ 'create type mynum as (num int, name varchar(20));', # forward sql
189
+ reverse_sql='drop type mynum;', # sql for removal
190
+ ),
191
+ ]
192
+ ```
193
+
194
+ 6) Generate migration `./manage.py makemigrations`:
195
+
196
+ ```
197
+ Migrations for 'app_name':
198
+ 0003_xxxx:
199
+ - Reverse alter SQL "make_sum"
200
+ - Create SQL "mynum"
201
+ - Alter SQL "make_sum"
202
+ - Alter SQL state "make_sum"
203
+ ```
204
+
205
+ You can take a look at the content this generated:
206
+
207
+ ```python
208
+ from django.db import migrations, models
209
+ import migrate_sql.operations
210
+
211
+
212
+ class Migration(migrations.Migration):
213
+ dependencies = [
214
+ ('app_name', '0002_xxxx'),
215
+ ]
216
+ operations = [
217
+ migrate_sql.operations.ReverseAlterSQL(
218
+ name='make_sum',
219
+ sql='drop function make_sum(int, int);',
220
+ reverse_sql='create or replace function make_sum(a int, b int) returns int as $$ begin return a + b; end; $$ language plpgsql;',
221
+ ),
222
+ migrate_sql.operations.CreateSQL(
223
+ name='mynum',
224
+ sql='create type mynum as (num int, name varchar(20));',
225
+ reverse_sql='drop type mynum;',
226
+ ),
227
+ migrate_sql.operations.AlterSQL(
228
+ name='make_sum',
229
+ sql='create or replace function make_sum(a mynum, b mynum) returns mynum as $$ begin return (a.num + b.num, \'result\')::mynum; end; $$ language plpgsql;',
230
+ reverse_sql='drop function make_sum(mynum, mynum);',
231
+ ),
232
+ migrate_sql.operations.AlterSQLState(
233
+ name='make_sum',
234
+ add_dependencies=(('app_name', 'mynum'),),
235
+ ),
236
+ ]
237
+ ```
238
+
239
+ **NOTE:** Previous function is completely dropped before creation
240
+ because definition of it changed. `CREATE OR REPLACE` would create
241
+ another version of it, so `DROP` makes it clean.
242
+
243
+ **If you put `replace=True` as kwarg to an `SQLItem`
244
+ definition, it will NOT drop + create it, but just rerun forward SQL,
245
+ which is `CREATE OR REPLACE` in this example.**
246
+
247
+ 7) Execute migration `./manage.py migrate`:
248
+
249
+
250
+ ```
251
+ Operations to perform:
252
+ Apply all migrations: app_name
253
+ Running migrations:
254
+ Rendering model states... DONE
255
+ Applying brands.0003_xxxx... OK
256
+ ```
257
+
258
+ Check results:
259
+
260
+ ```
261
+ db_name=# select make_sum((5, 'a')::mynum, (3, 'b')::mynum);
262
+ make_sum
263
+ ------------
264
+ (8,result)
265
+ (1 row)
266
+
267
+ db_name=# select make_sum(12, 15);
268
+ ERROR: function make_sum(integer, integer) does not exist
269
+ LINE 1: select make_sum(12, 15);
270
+ ^
271
+ HINT: No function matches the given name and argument types. You might need to add explicit type casts.
272
+ ```
273
+
274
+ For more examples see `tests`.
275
+
276
+ Feel free to [open new issues](https://github.com/browniebroke/django-migrate-sql-deux/issues).