11from contextlib import contextmanager
2+ from zlib import crc32
23
3- from django .db import connection , transaction
4+ from django .db import DEFAULT_DB_ALIAS , connection , connections , transaction
45from django .db .migrations .executor import MigrationExecutor
56
67
@@ -25,3 +26,114 @@ def migrations_are_complete() -> bool:
2526 executor = MigrationExecutor (connection )
2627 plan = executor .migration_plan (executor .loader .graph .leaf_nodes ())
2728 return not bool (plan )
29+
30+
31+ # NOTE: the django_pglocks_advisory_lock context manager was forked from the django-pglocks v1.0.4
32+ # that was licensed under the MIT license
33+
34+
35+ @contextmanager
36+ def django_pglocks_advisory_lock (lock_id , shared = False , wait = True , using = None ):
37+
38+ if using is None :
39+ using = DEFAULT_DB_ALIAS
40+
41+ # Assemble the function name based on the options.
42+
43+ function_name = 'pg_'
44+
45+ if not wait :
46+ function_name += 'try_'
47+
48+ function_name += 'advisory_lock'
49+
50+ if shared :
51+ function_name += '_shared'
52+
53+ release_function_name = 'pg_advisory_unlock'
54+ if shared :
55+ release_function_name += '_shared'
56+
57+ # Format up the parameters.
58+
59+ tuple_format = False
60+
61+ if isinstance (
62+ lock_id ,
63+ (
64+ list ,
65+ tuple ,
66+ ),
67+ ):
68+ if len (lock_id ) != 2 :
69+ raise ValueError ("Tuples and lists as lock IDs must have exactly two entries." )
70+
71+ if not isinstance (lock_id [0 ], int ) or not isinstance (lock_id [1 ], int ):
72+ raise ValueError ("Both members of a tuple/list lock ID must be integers" )
73+
74+ tuple_format = True
75+ elif isinstance (lock_id , str ):
76+ # Generates an id within postgres integer range (-2^31 to 2^31 - 1).
77+ # crc32 generates an unsigned integer in Py3, we convert it into
78+ # a signed integer using 2's complement (this is a noop in Py2)
79+ pos = crc32 (lock_id .encode ("utf-8" ))
80+ lock_id = (2 ** 31 - 1 ) & pos
81+ if pos & 2 ** 31 :
82+ lock_id -= 2 ** 31
83+ elif not isinstance (lock_id , int ):
84+ raise ValueError ("Cannot use %s as a lock id" % lock_id )
85+
86+ if tuple_format :
87+ base = "SELECT %s(%d, %d)"
88+ params = (
89+ lock_id [0 ],
90+ lock_id [1 ],
91+ )
92+ else :
93+ base = "SELECT %s(%d)"
94+ params = (lock_id ,)
95+
96+ acquire_params = (function_name ,) + params
97+
98+ command = base % acquire_params
99+ cursor = connections [using ].cursor ()
100+
101+ cursor .execute (command )
102+
103+ if not wait :
104+ acquired = cursor .fetchone ()[0 ]
105+ else :
106+ acquired = True
107+
108+ try :
109+ yield acquired
110+ finally :
111+ if acquired :
112+ release_params = (release_function_name ,) + params
113+
114+ command = base % release_params
115+ cursor .execute (command )
116+
117+ cursor .close ()
118+
119+
120+ @contextmanager
121+ def advisory_lock (* args , lock_session_timeout_milliseconds = 0 , ** kwargs ):
122+ if connection .vendor == "postgresql" :
123+ cur = None
124+ idle_in_transaction_session_timeout = None
125+ idle_session_timeout = None
126+ if lock_session_timeout_milliseconds > 0 :
127+ with connection .cursor () as cur :
128+ idle_in_transaction_session_timeout = cur .execute ("SHOW idle_in_transaction_session_timeout" ).fetchone ()[0 ]
129+ idle_session_timeout = cur .execute ("SHOW idle_session_timeout" ).fetchone ()[0 ]
130+ cur .execute (f"SET idle_in_transaction_session_timeout = '{ lock_session_timeout_milliseconds } '" )
131+ cur .execute (f"SET idle_session_timeout = '{ lock_session_timeout_milliseconds } '" )
132+ with django_pglocks_advisory_lock (* args , ** kwargs ) as internal_lock :
133+ yield internal_lock
134+ if lock_session_timeout_milliseconds > 0 :
135+ with connection .cursor () as cur :
136+ cur .execute (f"SET idle_in_transaction_session_timeout = '{ idle_in_transaction_session_timeout } '" )
137+ cur .execute (f"SET idle_session_timeout = '{ idle_session_timeout } '" )
138+ else :
139+ yield True
0 commit comments