123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- # -*- coding: utf-8 -*-
-
- ## @package EditorialModel.migrationhandler.sql
- # @brief A dummy migration handler
- #
- # According to it every modifications are possible
- #
-
- import EditorialModel
- from DataSource.dummy.migrationhandler import DummyMigrationHandler
- from EditorialModel.fieldtypes.generic import GenericFieldType
- from EditorialModel.model import Model
- from mosql.db import Database
- from Lodel.utils.mosql import create, alter_add
-
-
- ## Manage Model changes
- class SQLMigrationHandler(DummyMigrationHandler):
-
- fieldtype_to_sql = {
- 'char': "CHAR(255)",
- 'integer': 'INT'
- }
-
- def __init__(self, module=None, *conn_args, **conn_kargs):
- super(SQLMigrationHandler, self).__init__(False)
-
- self.db = Database(module, *conn_args, **conn_kargs)
- self._pk_column = (EditorialModel.classtypes.pk_name(), 'INTEGER PRIMARY KEY AUTO_INCREMENT')
- self._main_table_name = 'object'
- self._relation_table_name = 'relation'
-
- self._install_tables()
-
- ## @brief Record a change in the EditorialModel and indicate wether or not it is possible to make it
- # @note The states ( initial_state and new_state ) contains only fields that changes
- # @param model model : The EditorialModel.model object to provide the global context
- # @param uid int : The uid of the change EmComponent
- # @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.
- # @param new_state dict | None : dict with field name as key and field value as value. Representing the new state. None mean component deletion
- # @throw EditorialModel.exceptions.MigrationHandlerChangeError if the change was refused
- def register_change(self, model, uid, initial_state, new_state):
- # find type of component change
- if initial_state is None:
- state_change = 'new'
- elif new_state is None:
- state_change = 'del'
- else:
- state_change = 'upgrade'
-
- # call method to handle the database change
- component_name = Model.name_from_emclass(type(model.component(uid)))
- handler_func = component_name.lower() + '_' + state_change
- if hasattr(self, handler_func):
- getattr(self, handler_func)(model, uid, initial_state, new_state)
-
- # New Class, a table must be created
- def emclass_new(self, model, uid, initial_state, new_state):
- class_table_name = self._class_table_name(new_state['name'])
- self._query_bd(
- create(table=class_table_name, column=[self._pk_column])
- )
-
- # New Field, must create a column in Class table or in Class_Type relational attribute table
- # @todo common fields creation does not allow to add new common fields. It should
- def emfield_new(self, model, uid, initial_state, new_state):
-
- # field is of type rel2type, create the relational class_type table and return
- if new_state['fieldtype'] == 'rel2type':
- # find relational_type name, and class name of the field
- class_name = self._class_table_name_from_field(model, new_state)
- type_name = model.component(new_state['rel_to_type_id']).name
- table_name = self._relational_table_name(class_name, type_name)
- self._query_bd(
- create(table=table_name, column=[self._pk_column]),
- )
- return
-
- # Column creation
- #
- # field is internal, create a column in the objects table
- if new_state['internal']:
- if new_state['fieldtype'] == 'pk': # this column has already beeen created by self._install_tables()
- return
- if new_state['name'] in EditorialModel.classtypes.common_fields: # this column has already beeen created by self._install_tables()
- return
-
- # field is relational (rel_field_id), create a column in the class_type table
- elif new_state['rel_field_id']:
- class_name = self._class_table_name_from_field(model, new_state)
- rel_type_id = model.component(new_state['rel_field_id']).rel_to_type_id
- type_name = model.component(rel_type_id).name
- table_name = self._relational_table_name(class_name, type_name)
-
- # else create a column in the class table
- else:
- table_name = self._class_table_name_from_field(model, new_state)
-
- field_definition = self._fieldtype_definition(new_state['fieldtype'], new_state)
- self._query_bd(
- alter_add(table=table_name, column=[(new_state['name'],field_definition)])
- )
-
- ## convert fieldtype name to SQL definition
- def _fieldtype_definition(self, fieldtype, options):
- basic_type = GenericFieldType.from_name(fieldtype).ftype
- if basic_type == 'int':
- return 'INT'
- elif basic_type == 'char':
- max_length = options['max_length'] if 'max_length' in options else 255
- return 'CHAR(%s)' % max_length
- elif basic_type == 'text':
- return 'TEXT'
- elif basic_type == 'bool':
- return 'BOOLEAN'
- elif basic_type == 'datetime':
- definition = 'DATETIME'
- if 'now_on_create' in options and options['now_on_create']:
- definition += ' DEFAULT CURRENT_TIMESTAMP'
- #if 'now_on_update' in options and options['now_on_update']:
- #definition += ' ON UPDATE CURRENT_TIMESTAMP'
- return definition
-
- raise EditorialModel.exceptions.MigrationHandlerChangeError("Basic type '%s' of fieldtype '%s' is not compatible with SQL migration Handler" % basic_type, fieldtype)
-
- ## Test if internal tables must be created, create it if it must
- def _install_tables(self):
- # create common fields definition
- common_fields = [self._pk_column]
- for name, options in EditorialModel.classtypes.common_fields.items():
- if options['fieldtype'] != 'pk':
- common_fields.append((name, self._fieldtype_definition(options['fieldtype'], options)))
-
- # create common tables
- self._query_bd(
- create(table=self._main_table_name, column=common_fields),
- 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')])
- )
-
- def _query_bd(self, *queries):
- with self.db as cur:
- for query in queries:
- print(query)
- cur.execute(query)
-
- def _class_table_name(self, class_name):
- return 'class_' + class_name
-
- def _relational_table_name(self, class_name, type_name):
- return 'r2t_' + class_name + '_' + type_name
-
- def _class_table_name_from_field(self, model, field):
- class_id = field['class_id']
- class_name = model.component(class_id).name
- class_table_name = self._class_table_name(class_name)
- return class_table_name
|