pre-migration.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. # -*- coding: utf-8 -*-
  2. """
  3. Pre-migration script to prepare database schema for template integration
  4. This script runs BEFORE the module update to:
  5. 1. Make template_id nullable
  6. 2. Add workflow_template_id column
  7. 3. Prepare for constraint changes
  8. """
  9. import logging
  10. _logger = logging.getLogger(__name__)
  11. def migrate(cr, version):
  12. """Prepare database schema for template integration"""
  13. try:
  14. _logger.info("=" * 80)
  15. _logger.info("Starting pre-migration: Preparing database schema")
  16. _logger.info("=" * 80)
  17. # 1. Check if workflow_template_id column exists
  18. cr.execute("""
  19. SELECT column_name
  20. FROM information_schema.columns
  21. WHERE table_name = 'helpdesk_template_field'
  22. AND column_name = 'workflow_template_id'
  23. """)
  24. workflow_col_exists = cr.fetchone()
  25. if not workflow_col_exists:
  26. _logger.info("Adding workflow_template_id column to helpdesk_template_field...")
  27. cr.execute("""
  28. ALTER TABLE helpdesk_template_field
  29. ADD COLUMN workflow_template_id INTEGER
  30. """)
  31. _logger.info("✅ Column workflow_template_id added")
  32. else:
  33. _logger.info("Column workflow_template_id already exists")
  34. # 2. Add foreign key constraint if it doesn't exist
  35. cr.execute("""
  36. SELECT constraint_name
  37. FROM information_schema.table_constraints
  38. WHERE table_name = 'helpdesk_template_field'
  39. AND constraint_name = 'helpdesk_template_field_workflow_template_id_fkey'
  40. """)
  41. fk_exists = cr.fetchone()
  42. if not fk_exists:
  43. _logger.info("Adding foreign key constraint for workflow_template_id...")
  44. cr.execute("""
  45. ALTER TABLE helpdesk_template_field
  46. ADD CONSTRAINT helpdesk_template_field_workflow_template_id_fkey
  47. FOREIGN KEY (workflow_template_id)
  48. REFERENCES helpdesk_workflow_template(id)
  49. ON DELETE CASCADE
  50. """)
  51. _logger.info("✅ Foreign key constraint added")
  52. else:
  53. _logger.info("Foreign key constraint already exists")
  54. # 3. Make template_id nullable (if it's not already)
  55. cr.execute("""
  56. SELECT is_nullable
  57. FROM information_schema.columns
  58. WHERE table_name = 'helpdesk_template_field'
  59. AND column_name = 'template_id'
  60. """)
  61. template_nullable = cr.fetchone()
  62. if template_nullable and template_nullable[0] == 'NO':
  63. _logger.info("Making template_id nullable...")
  64. # First, drop the NOT NULL constraint by altering the column
  65. cr.execute("""
  66. ALTER TABLE helpdesk_template_field
  67. ALTER COLUMN template_id DROP NOT NULL
  68. """)
  69. _logger.info("✅ template_id is now nullable")
  70. else:
  71. _logger.info("template_id is already nullable")
  72. # 4. Add index on workflow_template_id for performance
  73. cr.execute("""
  74. SELECT indexname
  75. FROM pg_indexes
  76. WHERE tablename = 'helpdesk_template_field'
  77. AND indexname = 'helpdesk_template_field_workflow_template_id_index'
  78. """)
  79. index_exists = cr.fetchone()
  80. if not index_exists:
  81. _logger.info("Adding index on workflow_template_id...")
  82. cr.execute("""
  83. CREATE INDEX helpdesk_template_field_workflow_template_id_index
  84. ON helpdesk_template_field(workflow_template_id)
  85. """)
  86. _logger.info("✅ Index added")
  87. else:
  88. _logger.info("Index already exists")
  89. # 5. Drop ALL old UNIQUE constraints on helpdesk_template_field (they don't work well with NULLs)
  90. # Odoo names constraints as: {table_name}_{constraint_name}
  91. # We need to find and drop any UNIQUE constraints that involve template_id or workflow_template_id
  92. cr.execute("""
  93. SELECT DISTINCT tc.constraint_name
  94. FROM information_schema.table_constraints tc
  95. JOIN information_schema.key_column_usage kcu
  96. ON tc.constraint_name = kcu.constraint_name
  97. AND tc.table_schema = kcu.table_schema
  98. WHERE tc.table_name = 'helpdesk_template_field'
  99. AND tc.constraint_type = 'UNIQUE'
  100. AND (kcu.column_name IN ('template_id', 'workflow_template_id', 'field_id'))
  101. """)
  102. old_constraints = cr.fetchall()
  103. if old_constraints:
  104. _logger.info(f"Found {len(old_constraints)} UNIQUE constraint(s) to drop")
  105. for constraint in old_constraints:
  106. constraint_name = constraint[0]
  107. _logger.info(f"Dropping old constraint: {constraint_name}...")
  108. try:
  109. # Use format string with proper escaping
  110. cr.execute(f'ALTER TABLE helpdesk_template_field DROP CONSTRAINT IF EXISTS "{constraint_name}"')
  111. _logger.info(f"✅ Dropped constraint: {constraint_name}")
  112. except Exception as e:
  113. _logger.warning(f"Could not drop constraint {constraint_name}: {e}")
  114. # Try alternative method
  115. try:
  116. cr.execute("""
  117. ALTER TABLE helpdesk_template_field
  118. DROP CONSTRAINT IF EXISTS %s
  119. """, (constraint_name,))
  120. _logger.info(f"✅ Dropped constraint (method 2): {constraint_name}")
  121. except Exception as e2:
  122. _logger.error(f"Failed to drop constraint {constraint_name} with both methods: {e2}")
  123. else:
  124. _logger.info("No old UNIQUE constraints found to drop")
  125. # 6. Add partial unique indexes (work correctly with NULLs)
  126. # Index for template_id + field_id (only when template_id IS NOT NULL)
  127. cr.execute("""
  128. SELECT indexname
  129. FROM pg_indexes
  130. WHERE tablename = 'helpdesk_template_field'
  131. AND indexname = 'helpdesk_template_field_unique_template_field_idx'
  132. """)
  133. template_idx_exists = cr.fetchone()
  134. if not template_idx_exists:
  135. _logger.info("Adding partial unique index for template_id + field_id...")
  136. cr.execute("""
  137. CREATE UNIQUE INDEX helpdesk_template_field_unique_template_field_idx
  138. ON helpdesk_template_field(template_id, field_id)
  139. WHERE template_id IS NOT NULL
  140. """)
  141. _logger.info("✅ Partial unique index for template_id added")
  142. else:
  143. _logger.info("Partial unique index for template_id already exists")
  144. # Index for workflow_template_id + field_id (only when workflow_template_id IS NOT NULL)
  145. cr.execute("""
  146. SELECT indexname
  147. FROM pg_indexes
  148. WHERE tablename = 'helpdesk_template_field'
  149. AND indexname = 'helpdesk_template_field_unique_workflow_template_field_idx'
  150. """)
  151. workflow_idx_exists = cr.fetchone()
  152. if not workflow_idx_exists:
  153. _logger.info("Adding partial unique index for workflow_template_id + field_id...")
  154. cr.execute("""
  155. CREATE UNIQUE INDEX helpdesk_template_field_unique_workflow_template_field_idx
  156. ON helpdesk_template_field(workflow_template_id, field_id)
  157. WHERE workflow_template_id IS NOT NULL
  158. """)
  159. _logger.info("✅ Partial unique index for workflow_template_id added")
  160. else:
  161. _logger.info("Partial unique index for workflow_template_id already exists")
  162. # 7. Add CHECK constraint to ensure only one of template_id or workflow_template_id is set
  163. cr.execute("""
  164. SELECT constraint_name
  165. FROM information_schema.table_constraints
  166. WHERE table_name = 'helpdesk_template_field'
  167. AND constraint_name = 'helpdesk_template_field_check_template_or_workflow'
  168. """)
  169. check_exists = cr.fetchone()
  170. if not check_exists:
  171. _logger.info("Adding CHECK constraint for template_or_workflow...")
  172. cr.execute("""
  173. ALTER TABLE helpdesk_template_field
  174. ADD CONSTRAINT helpdesk_template_field_check_template_or_workflow
  175. CHECK ((template_id IS NOT NULL AND workflow_template_id IS NULL)
  176. OR (template_id IS NULL AND workflow_template_id IS NOT NULL))
  177. """)
  178. _logger.info("✅ CHECK constraint added")
  179. else:
  180. _logger.info("CHECK constraint already exists")
  181. cr.commit()
  182. _logger.info("=" * 80)
  183. _logger.info("✅ Pre-migration completed successfully")
  184. _logger.info("=" * 80)
  185. except Exception as e:
  186. _logger.error(f"❌ Error in pre-migration: {e}", exc_info=True)
  187. try:
  188. cr.rollback()
  189. except Exception:
  190. pass # Ignore rollback errors
  191. # Raise to prevent module update if pre-migration fails
  192. # This ensures database is in consistent state
  193. raise