Skip to content

Commit 56567a9

Browse files
KangOlaj-fuentesnseinlet
committed
[IMP] util.create_column: create FK
Allow to directly set the FK constraint when creating a new column. Part-of: #28 Co-authored-by: Alvaro Fuentes <[email protected]> Co-authored-by: Nicolas Seinlet <[email protected]>
1 parent 5f0d16c commit 56567a9

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

src/base/tests/test_util.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,30 @@ def test_parallel_rowcount(self):
446446
rowcount = util.explode_execute(cr, query, table="res_lang", bucket_size=10)
447447
self.assertEqual(rowcount, expected)
448448

449+
def test_create_column_with_fk(self):
450+
cr = self.env.cr
451+
self.assertFalse(util.column_exists(cr, "res_partner", "_test_lang_id"))
452+
453+
with self.assertRaises(ValueError):
454+
util.create_column(cr, "res_partner", "_test_lang_id", "int4", on_delete_action="SET NULL")
455+
456+
with self.assertRaises(ValueError):
457+
util.create_column(
458+
cr, "res_partner", "_test_lang_id", "int4", fk_table="res_lang", on_delete_action="INVALID"
459+
)
460+
461+
# this one should works
462+
util.create_column(cr, "res_partner", "_test_lang_id", "int4", fk_table="res_lang", on_delete_action="SET NULL")
463+
464+
target = util.target_of(cr, "res_partner", "_test_lang_id")
465+
self.assertEqual(target, ("res_lang", "id", "res_partner__test_lang_id_fkey"))
466+
467+
# code should be reentrant
468+
util.create_column(cr, "res_partner", "_test_lang_id", "int4", fk_table="res_lang", on_delete_action="SET NULL")
469+
470+
target = util.target_of(cr, "res_partner", "_test_lang_id")
471+
self.assertEqual(target, ("res_lang", "id", "res_partner__test_lang_id_fkey"))
472+
449473

450474
class TestORM(UnitTestCase):
451475
def test_create_cron(self):

src/util/pg.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
_logger = logging.getLogger(__name__)
3131

32+
ON_DELETE_ACTIONS = frozenset(("SET NULL", "CASCADE", "RESTRICT", "NO ACTION", "SET DEFAULT"))
33+
3234

3335
class PGRegexp(str):
3436
"""
@@ -290,8 +292,25 @@ def create_column(cr, table, column, definition, **kwargs):
290292
# Manual PEP 3102
291293
no_def = object()
292294
default = kwargs.pop("default", no_def)
295+
fk_table = kwargs.pop("fk_table", no_def)
296+
on_delete_action = kwargs.pop("on_delete_action", no_def)
293297
if kwargs:
294298
raise TypeError("create_column() got an unexpected keyword argument %r" % kwargs.popitem()[0])
299+
300+
fk = ""
301+
if fk_table is not no_def:
302+
if on_delete_action is no_def:
303+
on_delete_action = "NO ACTION"
304+
elif on_delete_action not in ON_DELETE_ACTIONS:
305+
raise ValueError("unexpected value for the `on_delete_action` argument: %r" % (on_delete_action,))
306+
fk = (
307+
sql.SQL("REFERENCES {}(id) ON DELETE {}")
308+
.format(sql.Identifier(fk_table), sql.SQL(on_delete_action))
309+
.as_string(cr._cnx)
310+
)
311+
elif on_delete_action is not no_def:
312+
raise ValueError("`on_delete_action` argument can only be used if `fk_table` argument is set.")
313+
295314
aliases = {
296315
"boolean": "bool",
297316
"smallint": "int2",
@@ -312,13 +331,15 @@ def create_column(cr, table, column, definition, **kwargs):
312331
if curtype:
313332
if curtype != definition:
314333
_logger.error("%s.%s already exists but is %r instead of %r", table, column, curtype, definition)
334+
if fk_table is not no_def:
335+
create_fk(cr, table, column, fk_table, on_delete_action)
315336
if default is not no_def:
316337
query = 'UPDATE "{0}" SET "{1}" = %s WHERE "{1}" IS NULL'.format(table, column)
317338
query = cr.mogrify(query, [default]).decode()
318339
parallel_execute(cr, explode_query_range(cr, query, table=table))
319340
return False
320341

321-
create_query = """ALTER TABLE "%s" ADD COLUMN "%s" %s""" % (table, column, definition)
342+
create_query = """ALTER TABLE "%s" ADD COLUMN "%s" %s %s""" % (table, column, definition, fk)
322343
if default is no_def:
323344
cr.execute(create_query)
324345
else:
@@ -328,7 +349,7 @@ def create_column(cr, table, column, definition, **kwargs):
328349

329350

330351
def create_fk(cr, table, column, fk_table, on_delete_action=""):
331-
assert on_delete_action in {"SET NULL", "CASCADE", "RESTRICT", "NO ACTION", "SET DEFAULT", ""}
352+
assert on_delete_action in ON_DELETE_ACTIONS | {""}
332353
on_delete = "" if not on_delete_action else "ON DELETE {}".format(on_delete_action)
333354
cr.execute(
334355
"""

0 commit comments

Comments
 (0)