No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

migrationhandler__future__.py 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. # -*- coding: utf-8 -*-
  2. ## @package EditorialModel.migrationhandler.sql
  3. # @brief A dummy migration handler
  4. #
  5. # According to it every modifications are possible
  6. #
  7. import EditorialModel
  8. from DataSource.dummy.migrationhandler import DummyMigrationHandler
  9. from EditorialModel.fieldtypes.generic import GenericFieldType
  10. from EditorialModel.model import Model
  11. from mosql.db import Database
  12. from Lodel.utils.mosql import create, alter_add
  13. ## Manage Model changes
  14. class SQLMigrationHandler(DummyMigrationHandler):
  15. fieldtype_to_sql = {
  16. 'char': "CHAR(255)",
  17. 'integer': 'INT'
  18. }
  19. def __init__(self, module=None, *conn_args, **conn_kargs):
  20. super(SQLMigrationHandler, self).__init__(False)
  21. self.db = Database(module, *conn_args, **conn_kargs)
  22. self._pk_column = (EditorialModel.classtypes.pk_name(), 'INTEGER PRIMARY KEY AUTO_INCREMENT')
  23. self._main_table_name = 'object'
  24. self._relation_table_name = 'relation'
  25. self._install_tables()
  26. ## @brief Record a change in the EditorialModel and indicate wether or not it is possible to make it
  27. # @note The states ( initial_state and new_state ) contains only fields that changes
  28. # @param model model : The EditorialModel.model object to provide the global context
  29. # @param uid int : The uid of the change EmComponent
  30. # @param initial_state dict | None : dict with field name as key and field value as value. Representing the original state. None mean creation of a new component.
  31. # @param new_state dict | None : dict with field name as key and field value as value. Representing the new state. None mean component deletion
  32. # @throw EditorialModel.exceptions.MigrationHandlerChangeError if the change was refused
  33. def register_change(self, model, uid, initial_state, new_state):
  34. # find type of component change
  35. if initial_state is None:
  36. state_change = 'new'
  37. elif new_state is None:
  38. state_change = 'del'
  39. else:
  40. state_change = 'upgrade'
  41. # call method to handle the database change
  42. component_name = Model.name_from_emclass(type(model.component(uid)))
  43. handler_func = component_name.lower() + '_' + state_change
  44. if hasattr(self, handler_func):
  45. getattr(self, handler_func)(model, uid, initial_state, new_state)
  46. # New Class, a table must be created
  47. def emclass_new(self, model, uid, initial_state, new_state):
  48. class_table_name = self._class_table_name(new_state['name'])
  49. self._query_bd(
  50. create(table=class_table_name, column=[self._pk_column])
  51. )
  52. # New Field, must create a column in Class table or in Class_Type relational attribute table
  53. # @todo common fields creation does not allow to add new common fields. It should
  54. def emfield_new(self, model, uid, initial_state, new_state):
  55. # field is of type rel2type, create the relational class_type table and return
  56. if new_state['fieldtype'] == 'rel2type':
  57. # find relational_type name, and class name of the field
  58. class_name = self._class_table_name_from_field(model, new_state)
  59. type_name = model.component(new_state['rel_to_type_id']).name
  60. table_name = self._relational_table_name(class_name, type_name)
  61. self._query_bd(
  62. create(table=table_name, column=[self._pk_column]),
  63. )
  64. return
  65. # Column creation
  66. #
  67. # field is internal, create a column in the objects table
  68. if new_state['internal']:
  69. if new_state['fieldtype'] == 'pk': # this column has already beeen created by self._install_tables()
  70. return
  71. if new_state['name'] in EditorialModel.classtypes.common_fields: # this column has already beeen created by self._install_tables()
  72. return
  73. # field is relational (rel_field_id), create a column in the class_type table
  74. elif new_state['rel_field_id']:
  75. class_name = self._class_table_name_from_field(model, new_state)
  76. rel_type_id = model.component(new_state['rel_field_id']).rel_to_type_id
  77. type_name = model.component(rel_type_id).name
  78. table_name = self._relational_table_name(class_name, type_name)
  79. # else create a column in the class table
  80. else:
  81. table_name = self._class_table_name_from_field(model, new_state)
  82. field_definition = self._fieldtype_definition(new_state['fieldtype'], new_state)
  83. self._query_bd(
  84. alter_add(table=table_name, column=[(new_state['name'],field_definition)])
  85. )
  86. ## convert fieldtype name to SQL definition
  87. def _fieldtype_definition(self, fieldtype, options):
  88. basic_type = GenericFieldType.from_name(fieldtype).ftype
  89. if basic_type == 'int':
  90. return 'INT'
  91. elif basic_type == 'char':
  92. max_length = options['max_length'] if 'max_length' in options else 255
  93. return 'CHAR(%s)' % max_length
  94. elif basic_type == 'text':
  95. return 'TEXT'
  96. elif basic_type == 'bool':
  97. return 'BOOLEAN'
  98. elif basic_type == 'datetime':
  99. definition = 'DATETIME'
  100. if 'now_on_create' in options and options['now_on_create']:
  101. definition += ' DEFAULT CURRENT_TIMESTAMP'
  102. #if 'now_on_update' in options and options['now_on_update']:
  103. #definition += ' ON UPDATE CURRENT_TIMESTAMP'
  104. return definition
  105. raise EditorialModel.exceptions.MigrationHandlerChangeError("Basic type '%s' of fieldtype '%s' is not compatible with SQL migration Handler" % basic_type, fieldtype)
  106. ## Test if internal tables must be created, create it if it must
  107. def _install_tables(self):
  108. # create common fields definition
  109. common_fields = [self._pk_column]
  110. for name, options in EditorialModel.classtypes.common_fields.items():
  111. if options['fieldtype'] != 'pk':
  112. common_fields.append((name, self._fieldtype_definition(options['fieldtype'], options)))
  113. # create common tables
  114. self._query_bd(
  115. create(table=self._main_table_name, column=common_fields),
  116. create(table=self._relation_table_name, column=[('relation_id','INTEGER PRIMARY KEY AUTOINCREMENT'), ('superior_id','INT'), ('subdordinate_id','INT'), ('nature','CHAR(255)'), ('depth','INT'), ('rank','INT')])
  117. )
  118. def _query_bd(self, *queries):
  119. with self.db as cur:
  120. for query in queries:
  121. print(query)
  122. cur.execute(query)
  123. def _class_table_name(self, class_name):
  124. return 'class_' + class_name
  125. def _relational_table_name(self, class_name, type_name):
  126. return 'r2t_' + class_name + '_' + type_name
  127. def _class_table_name_from_field(self, model, field):
  128. class_id = field['class_id']
  129. class_name = model.component(class_id).name
  130. class_table_name = self._class_table_name(class_name)
  131. return class_table_name