1414from django .contrib .auth .mixins import LoginRequiredMixin
1515from django .contrib .postgres .search import TrigramSimilarity
1616from django .core .exceptions import ObjectDoesNotExist , PermissionDenied
17+ from django .db import DatabaseError , transaction
1718from django .db .models import Q
1819from django .db .utils import IntegrityError
1920from django .http import (
@@ -482,6 +483,100 @@ def validate_column_names(self, column_definitions):
482483 if len (colname ) > MAX_COL_NAME_LENGTH :
483484 raise APIError (f"Column name is too long! { err_msg } " )
484485
486+ def oep_create_table_transaction (
487+ self ,
488+ django_schema_object ,
489+ schema ,
490+ table ,
491+ column_definitions ,
492+ constraint_definitions ,
493+ ):
494+ """
495+ This method handles atomic table creation transactions on the OEP. It
496+ attempts to create first the django table objects and stored it in
497+ dataedit_tables table. Then it attempts to create the OEDB table.
498+ If there is an error raised during the first two steps the function
499+ will cleanup any table object or table artifacts created during the
500+ process. The order of execution matters, it should always first
501+ create the django table object.
502+
503+ Params:
504+ django_schema_object: The schema object stored in the django
505+ database
506+ schema:
507+ table
508+ column_definitions
509+ constraint_definitions
510+
511+ returns:
512+ table_object: The django table objects that was created
513+ """
514+
515+ try :
516+ with transaction .atomic ():
517+ # First create the table object in the django database.
518+ table_object = self ._create_table_object (django_schema_object , table )
519+ # Then attempt to create the OEDB table to check
520+ # if creation will succeed - action includes checks
521+ # and will raise api errors
522+ actions .table_create (
523+ schema , table , column_definitions , constraint_definitions
524+ )
525+ except DatabaseError as e :
526+ # remove any oedb table artifacts left after table creation
527+ # transaction failed
528+ self .__remove_oedb_table_on_exception_raised_during_creation_transaction (
529+ table , schema
530+ )
531+
532+ # also remove any django table object
533+ # find the created django table object
534+ object_to_delete = DBTable .objects .filter (
535+ name = table , schema = django_schema_object
536+ )
537+ # delete it if it exists
538+ if object_to_delete .exists ():
539+ object_to_delete .delete ()
540+
541+ raise APIError (
542+ message = "Error during table creation transaction. All table fragments"
543+ f"have been removed. For further details see: { e } "
544+ )
545+
546+ # for now only return the django table object
547+ # TODO: Check if is necessary to return the response dict returned by the oedb
548+ # table creation function
549+ return table_object
550+
551+ def __remove_oedb_table_on_exception_raised_during_creation_transaction (
552+ self , table , schema
553+ ):
554+ """
555+ This private method handles removing a table form the OEDB only for the case
556+ where an error was raised during table creation. It specifically will delete
557+ the OEDB table created by the user and also the edit_ meta(revision) table
558+ that is automatically created in the background.
559+ """
560+ # find the created oedb table
561+ if actions .has_table ({"table" : table , "schema" : schema }):
562+ # get table and schema names, also for meta(revision) tables
563+ schema , table = actions .get_table_name (schema , table )
564+ meta_schema = actions .get_meta_schema_name (schema )
565+
566+ # drop the revision table with edit_ prefix
567+ edit_table = actions .get_edit_table_name (schema , table )
568+ actions ._get_engine ().execute (
569+ 'DROP TABLE "{schema}"."{table}" CASCADE;' .format (
570+ schema = meta_schema , table = edit_table
571+ )
572+ )
573+ # drop the data table
574+ actions ._get_engine ().execute (
575+ 'DROP TABLE "{schema}"."{table}" CASCADE;' .format (
576+ schema = schema , table = table
577+ )
578+ )
579+
485580 @load_cursor ()
486581 def __create_table (
487582 self ,
@@ -510,11 +605,13 @@ def __create_table(
510605 raise embargo_error
511606
512607 if embargo_payload_check :
513- table_object = self ._create_table_object (schema_object , table )
514- actions .table_create (
515- schema , table , column_definitions , constraint_definitions
608+ table_object = self .oep_create_table_transaction (
609+ django_schema_object = schema_object ,
610+ table = table ,
611+ schema = schema ,
612+ column_definitions = column_definitions ,
613+ constraint_definitions = constraint_definitions ,
516614 )
517-
518615 self ._apply_embargo (table_object , embargo_data )
519616
520617 if metadata :
@@ -534,9 +631,12 @@ def __create_table(
534631 )
535632
536633 else :
537- table_object = self ._create_table_object (schema_object , table )
538- actions .table_create (
539- schema , table , column_definitions , constraint_definitions
634+ table_object = self .oep_create_table_transaction (
635+ django_schema_object = schema_object ,
636+ table = table ,
637+ schema = schema ,
638+ column_definitions = column_definitions ,
639+ constraint_definitions = constraint_definitions ,
540640 )
541641 self ._assign_table_holder (request .user , schema , table )
542642
0 commit comments