1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2026-07-06 23:50:48 +02:00
lodel2_mirror/DataSource/MySQL/migrationhandler.py

156 lines
6.9 KiB
Python

# -*- 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 AUTOINCREMENT')
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