Нет описания
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

migrationhandler.py 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. # -*- coding: utf-8 -*-
  2. import copy
  3. import pymysql
  4. from Lodel.settings import Settings
  5. import EditorialModel
  6. import EditorialModel.classtypes
  7. import EditorialModel.fieldtypes
  8. import EditorialModel.fieldtypes.generic
  9. from DataSource.MySQL import utils
  10. from DataSource.dummy.migrationhandler import DummyMigrationHandler
  11. # The global MH algorithm is as follow :
  12. # A create_table(table_name, pk_name, pk_opt) method that create a table
  13. # with one pk field
  14. # An add_column(table_name, field_name, field_opt) method that add a column to a table
  15. #
  16. # The create_default_table method will call both methods to create the object and relation tables
  17. #
  18. # Supported operations :
  19. # - EmClass creation
  20. # - EmClass deletion
  21. # - EmField creation
  22. # - EmField deletion
  23. # - rel2type attribute creation
  24. # - rel2type attribute deletion
  25. #
  26. # Unsupported operations :
  27. # - EmClass rename
  28. # - EmField rename
  29. # - rel2type field rename
  30. # - rel2type attribute rename
  31. # - EmFieldType changes
  32. #
  33. # @todo Unified datasources and migration handlers via utils functions
  34. ## @brief Modify a MySQL database given editorial model changes
  35. class MysqlMigrationHandler(DummyMigrationHandler):
  36. ## @brief Construct a MysqlMigrationHandler
  37. # @param module : sql module
  38. # @param conn_args dict : A dict containing connection options
  39. def __init__(self, module=pymysql, conn_args=None, db_engine='InnoDB', **kwargs):
  40. # Database connection
  41. self._dbmodule = module
  42. if conn_args is None:
  43. conn_args = copy.copy(Settings.get('datasource')['default'])
  44. self._dbmodule = conn_args['module']
  45. del conn_args['module']
  46. self.db_conn = self._dbmodule.connect(**conn_args)
  47. # Fetch options
  48. mh_settings = Settings.migration_options
  49. self.debug = kwargs['debug'] if 'debug' in kwargs else Settings.debug_sql
  50. self.dryrun = kwargs['dryrun'] if 'dryrun' in kwargs else mh_settings['dryrun']
  51. self.foreign_keys = kwargs['foreign_keys'] if 'foreign_keys' in kwargs else mh_settings['foreign_keys']
  52. self.drop_if_exists = kwargs['drop_if_exists'] if 'drop_if_exists' in kwargs else mh_settings['drop_if_exists']
  53. self.db_engine = db_engine
  54. #Create default tables
  55. self._create_default_tables(self.drop_if_exists)
  56. ## @brief Modify the db given an EM change
  57. # @param em model : The EditorialModel.model object to provide the global context
  58. # @param uid int : The uid of the change EmComponent
  59. # @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.
  60. # @param new_state dict | None : dict with field name as key and field value as value. Representing the new state. None mean component deletion
  61. # @throw EditorialModel.exceptions.MigrationHandlerChangeError if the change was refused
  62. def register_change(self, em, uid, initial_state, new_state, engine=None):
  63. if engine is None:
  64. engine = self.db_engine
  65. if isinstance(em.component(uid), EditorialModel.classes.EmClass):
  66. if initial_state is None:
  67. #EmClass creation
  68. self.create_emclass_table(em, uid, engine)
  69. elif new_state is None:
  70. #EmClass deletion
  71. self.delete_emclass_table(em, uid)
  72. elif isinstance(em.component(uid), EditorialModel.fields.EmField):
  73. emfield = em.component(uid)
  74. if emfield.rel_field_id is None:
  75. #non relationnal field
  76. if initial_state is None:
  77. #non relationnal EmField creation
  78. if emfield.name not in EditorialModel.classtypes.common_fields.keys():
  79. self.add_col_from_emfield(em, uid)
  80. elif new_state is None:
  81. #non relationnal EmField deletion
  82. if emfield.name not in EditorialModel.classtypes.common_fields.keys():
  83. self.delete_col_from_emfield(em, uid)
  84. else:
  85. #relationnal field
  86. if initial_state is None:
  87. #Rel2type attr creation
  88. self.add_relationnal_field(em, uid)
  89. elif new_state is None:
  90. #Rel2type attr deletion
  91. self.del_relationnal_field(em, uid)
  92. ## @brief dumdumdummy
  93. # @note implemented to avoid the log message of EditorialModel.migrationhandler.dummy.DummyMigrationHandler
  94. def register_model_state(self, em, state_hash):
  95. pass
  96. ## @brief Add a relationnal field
  97. # Add a rel2type attribute
  98. # @note this function handles the table creation
  99. # @param em Model : EditorialModel.model.Model instance
  100. # @param rfuid int : Relationnal field uid
  101. def add_relationnal_field(self, edmod, rfuid):
  102. emfield = edmod.component(rfuid)
  103. if not isinstance(emfield, EditorialModel.fields.EmField):
  104. raise ValueError("The given uid is not an EmField uid")
  105. r2tf = edmod.component(emfield.rel_field_id)
  106. tname = self._r2t2table_name(edmod, r2tf)
  107. pkname, pkftype = self._relation_pk
  108. #If not exists create a relational table
  109. self._create_table(tname, pkname, pkftype, self.db_engine, if_exists='nothing')
  110. #Add a foreign key if wanted
  111. if self.foreign_keys:
  112. self._add_fk(tname, utils.common_tables['relation'], pkname, pkname)
  113. #Add the column
  114. self._add_column(tname, emfield.name, emfield.fieldtype_instance())
  115. #Update table triggers
  116. self._generate_triggers(tname, self._r2type2cols(edmod, r2tf))
  117. ## @brief Delete a rel2type attribute
  118. #
  119. # Delete a rel2type attribute
  120. # @note this method handles the table deletion
  121. # @param em Model : EditorialModel.model.Model instance
  122. # @param rfuid int : Relationnal field uid
  123. def del_relationnal_field(self, edmod, rfuid):
  124. emfield = edmod.component(rfuid)
  125. if not isinstance(emfield, EditorialModel.fields.EmField):
  126. raise ValueError("The given uid is not an EmField uid")
  127. r2tf = edmod.component(emfield.rel_field_id)
  128. tname = self._r2t2table_name(edmod, r2tf)
  129. if len(self._r2type2cols(edmod, r2tf)) == 1:
  130. #The table can be deleted (no more attribute for this rel2type)
  131. self._query("""DROP TABLE {table_name}""".format(table_name=tname))
  132. else:
  133. self._del_column(tname, emfield.name)
  134. #Update table triggers
  135. self._generate_triggers(tname, self._r2type2cols(edmod, r2tf))
  136. ## @brief Given an EmField uid add a column to the corresponding table
  137. # @param em Model : A Model instance
  138. # @param uid int : An EmField uid
  139. def add_col_from_emfield(self, edmod, uid):
  140. emfield = edmod.component(uid)
  141. if not isinstance(emfield, EditorialModel.fields.EmField):
  142. raise ValueError("The given uid is not an EmField uid")
  143. emclass = emfield.em_class
  144. tname = utils.object_table_name(emclass.name)
  145. self._add_column(tname, emfield.name, emfield.fieldtype_instance())
  146. # Refresh the table triggers
  147. cols_l = self._class2cols(emclass)
  148. self._generate_triggers(tname, cols_l)
  149. ## @brief Given a class uid create the coressponding table
  150. # @param em Model : A Model instance
  151. # @param uid int : An EmField uid
  152. def create_emclass_table(self, edmod, uid, engine):
  153. emclass = edmod.component(uid)
  154. if not isinstance(emclass, EditorialModel.classes.EmClass):
  155. raise ValueError("The given uid is not an EmClass uid")
  156. pkname, pktype = self._object_pk
  157. table_name = utils.object_table_name(emclass.name)
  158. self._create_table(table_name, pkname, pktype, engine=engine)
  159. if self.foreign_keys:
  160. self._add_fk(table_name, utils.common_tables['object'], pkname, pkname)
  161. ## @brief Given an EmClass uid delete the corresponding table
  162. # @param em Model : A Model instance
  163. # @param uid int : An EmField uid
  164. def delete_emclass_table(self, edmod, uid):
  165. emclass = edmod.component(uid)
  166. if not isinstance(emclass, EditorialModel.classes.EmClass):
  167. raise ValueError("The give uid is not an EmClass uid")
  168. tname = utils.object_table_name(emclass.name)
  169. # Delete the table triggers to prevent errors
  170. self._generate_triggers(tname, dict())
  171. tname = utils.escape_idname(tname)
  172. self._query("""DROP TABLE {table_name};""".format(table_name=tname))
  173. ## @brief Given an EmField delete the corresponding column
  174. # @param em Model : an @ref EditorialModel.model.Model instance
  175. # @param uid int : an EmField uid
  176. def delete_col_from_emfield(self, edmod, uid):
  177. emfield = edmod.component(uid)
  178. if not isinstance(emfield, EditorialModel.fields.EmField):
  179. raise ValueError("The given uid is not an EmField uid")
  180. emclass = emfield.em_class
  181. tname = utils.object_table_name(emclass.name)
  182. # Delete the table triggers to prevent errors
  183. self._generate_triggers(tname, dict())
  184. self._del_column(tname, emfield.name)
  185. # Refresh the table triggers
  186. cols_l = self._class2cols(emclass)
  187. self._generate_triggers(tname, cols_l)
  188. ## @brief Delete a column from a table
  189. # @param tname str : The table name
  190. # @param fname str : The column name
  191. def _del_column(self, tname, fname):
  192. tname = utils.escape_idname(tname)
  193. fname = utils.escape_idname(fname)
  194. self._query("""ALTER TABLE {table_name} DROP COLUMN {col_name};""".format(table_name=tname, col_name=fname))
  195. ## @brief Construct a table name given a rela2type EmField instance
  196. # @param em Model : A Model instance
  197. # @param emfield EmField : An EmField instance
  198. # @return a table name
  199. def _r2t2table_name(self, edmod, emfield):
  200. emclass = emfield.em_class
  201. emtype = edmod.component(emfield.rel_to_type_id)
  202. return utils.r2t_table_name(emclass.name, emtype.name)
  203. ## @brief Generate a columns_fieldtype dict given a rel2type EmField
  204. # @param em Model : an @ref EditorialModel.model.Model instance
  205. # @param emfield EmField : and @ref EditorialModel.fields.EmField instance
  206. def _r2type2cols(self, edmod, emfield):
  207. return {f.name: f.fieldtype_instance() for f in edmod.components('EmField') if f.rel_field_id == emfield.uid}
  208. ## @brief Generate a columns_fieldtype dict given an EmClass
  209. # @param emclass EmClass : An EmClass instance
  210. # @return A dict with column name as key and EmFieldType instance as value
  211. def _class2cols(self, emclass):
  212. if not isinstance(emclass, EditorialModel.classes.EmClass):
  213. raise ValueError("The given uid is not an EmClass uid")
  214. return {f.name: f.fieldtype_instance() for f in emclass.fields() if f.name not in EditorialModel.classtypes.common_fields.keys()}
  215. ## @brief Create object and relations tables
  216. # @param drop_if_exist bool : If true drop tables if exists
  217. def _create_default_tables(self, drop_if_exist=False):
  218. if_exists = 'drop' if drop_if_exist else 'nothing'
  219. #Object table
  220. tname = utils.common_tables['object']
  221. pk_name, pk_ftype = self._object_pk
  222. self._create_table(tname, pk_name, pk_ftype, engine=self.db_engine, if_exists=if_exists)
  223. #Adding columns
  224. cols = {fname: self._common_field_to_ftype(fname) for fname in EditorialModel.classtypes.common_fields}
  225. for fname, ftype in cols.items():
  226. if fname != pk_name:
  227. self._add_column(tname, fname, ftype)
  228. #Creating triggers
  229. self._generate_triggers(tname, cols)
  230. object_tname = tname
  231. #Relation table
  232. tname = utils.common_tables['relation']
  233. pk_name, pk_ftype = self._relation_pk
  234. self._create_table(tname, pk_name, pk_ftype, engine=self.db_engine, if_exists=if_exists)
  235. #Adding columns
  236. for fname, ftype in self._relation_cols.items():
  237. self._add_column(tname, fname, ftype)
  238. #Creating triggers
  239. self._generate_triggers(tname, self._relation_cols)
  240. # Creating foreign keys between relation and object table
  241. sup_cname, sub_cname = self.get_sup_and_sub_cols()
  242. self._add_fk(tname, object_tname, sup_cname, self._object_pk[0], 'fk_relations_superiors')
  243. self._add_fk(tname, object_tname, sub_cname, self._object_pk[0], 'fk_relations_subordinate')
  244. ## @brief Returns the fieldname for superior and subordinate in relation table
  245. # @return a tuple (superior_name, subordinate_name)
  246. @classmethod
  247. def get_sup_and_sub_cols(cls):
  248. sup = None
  249. sub = None
  250. for fname, finfo in EditorialModel.classtypes.relations_common_fields.items():
  251. if finfo['fieldtype'] == 'leo':
  252. if finfo['superior']:
  253. sup = fname
  254. else:
  255. sub = fname
  256. return utils.column_name(sup), utils.column_name(sub)
  257. ## @brief Create a table with primary key
  258. # @param table_name str : table name
  259. # @param pk_name str : pk column name
  260. # @param pk_specs str : see @ref _field_to_sql()
  261. # @param engine str : The engine to use with this table
  262. # @param charset str : The charset of this table
  263. # @param if_exist str : takes values in ['nothing', 'drop']
  264. def _create_table(self, table_name, pk_name, pk_ftype, engine, charset='utf8', if_exists='nothing'):
  265. #Escaped table name
  266. etname = utils.escape_idname(table_name)
  267. pk_type = self._field_to_type(pk_ftype)
  268. pk_specs = self._field_to_specs(pk_ftype)
  269. if if_exists == 'drop':
  270. self._query("""DROP TABLE IF EXISTS {table_name};""".format(table_name=etname))
  271. qres = """
  272. CREATE TABLE {table_name} (
  273. {pk_name} {pk_type} {pk_specs},
  274. PRIMARY KEY({pk_name})
  275. ) ENGINE={engine} DEFAULT CHARSET={charset};"""
  276. elif if_exists == 'nothing':
  277. qres = """CREATE TABLE IF NOT EXISTS {table_name} (
  278. {pk_name} {pk_type} {pk_specs},
  279. PRIMARY KEY({pk_name})
  280. ) ENGINE={engine} DEFAULT CHARSET={charset};"""
  281. else:
  282. raise ValueError("Unexpected value for argument if_exists '%s'." % if_exists)
  283. self._query(qres.format(
  284. table_name=utils.escape_idname(table_name),
  285. pk_name=utils.escape_idname(pk_name),
  286. pk_type=pk_type,
  287. pk_specs=pk_specs,
  288. engine=engine,
  289. charset=charset
  290. ))
  291. ## @brief Add a column to a table
  292. # @param table_name str : The table name
  293. # @param col_name str : The columns name
  294. # @param col_fieldtype EmFieldype the fieldtype
  295. # @return True if the column was added else return False
  296. def _add_column(self, table_name, col_name, col_fieldtype, drop_if_exists=False):
  297. col_name = utils.column_name(col_name)
  298. add_col = """ALTER TABLE {table_name}
  299. ADD COLUMN {col_name} {col_type} {col_specs};"""
  300. etname = utils.escape_idname(table_name)
  301. ecname = utils.escape_idname(col_name)
  302. add_col = add_col.format(
  303. table_name=etname,
  304. col_name=ecname,
  305. col_type=self._field_to_type(col_fieldtype),
  306. col_specs=self._field_to_specs(col_fieldtype),
  307. )
  308. try:
  309. self._query(add_col)
  310. except self._dbmodule.err.InternalError:
  311. if drop_if_exists:
  312. self._del_column(table_name, col_name)
  313. self._add_column(table_name, col_name, col_fieldtype, drop_if_exists)
  314. else:
  315. #LOG
  316. print("Aborded, column `%s` exists" % col_name)
  317. return False
  318. return True
  319. ## @brief Add a foreign key
  320. # @param src_table_name str : The name of the table where we will add the FK
  321. # @param dst_table_name str : The name of the table the FK will point on
  322. # @param src_col_name str : The name of the concerned column in the src_table
  323. # @param dst_col_name str : The name of the concerned column in the dst_table
  324. def _add_fk(self, src_table_name, dst_table_name, src_col_name, dst_col_name, fk_name=None):
  325. stname = utils.escape_idname(src_table_name)
  326. dtname = utils.escape_idname(dst_table_name)
  327. scname = utils.escape_idname(src_col_name)
  328. dcname = utils.escape_idname(dst_col_name)
  329. if fk_name is None:
  330. fk_name = utils.get_fk_name(src_table_name, dst_table_name)
  331. self._del_fk(src_table_name, dst_table_name, fk_name)
  332. self._query("""ALTER TABLE {src_table}
  333. ADD CONSTRAINT {fk_name}
  334. FOREIGN KEY ({src_col}) references {dst_table}({dst_col});""".format(
  335. fk_name=utils.escape_idname(fk_name),
  336. src_table=stname,
  337. src_col=scname,
  338. dst_table=dtname,
  339. dst_col=dcname
  340. ))
  341. ## @brief Given a source and a destination table, delete the corresponding FK
  342. # @param src_table_name str : The name of the table where the FK is
  343. # @param dst_table_name str : The name of the table the FK point on
  344. # @warning fails silently
  345. def _del_fk(self, src_table_name, dst_table_name, fk_name=None):
  346. if fk_name is None:
  347. fk_name = utils.escape_idname(utils.get_fk_name(src_table_name, dst_table_name))
  348. try:
  349. self._query("""ALTER TABLE {src_table}
  350. DROP FOREIGN KEY {fk_name}""".format(
  351. src_table=utils.escape_idname(src_table_name),
  352. fk_name=fk_name
  353. ))
  354. except self._dbmodule.err.InternalError:
  355. # If the FK don't exists we do not care
  356. pass
  357. ## @brief Generate triggers given a table_name and its columns fieldtypes
  358. # @param table_name str : Table name
  359. # @param cols_ftype dict : with col name as key and column fieldtype as value
  360. def _generate_triggers(self, table_name, cols_ftype):
  361. colval_l_upd = dict() # param for update trigger
  362. colval_l_ins = dict() # param for insert trigger
  363. for cname, cftype in cols_ftype.items():
  364. if cftype.ftype == 'datetime':
  365. if cftype.now_on_update:
  366. colval_l_upd[cname] = 'NOW()'
  367. if cftype.now_on_create:
  368. colval_l_ins[cname] = 'NOW()'
  369. self._table_trigger(table_name, 'UPDATE', colval_l_upd)
  370. self._table_trigger(table_name, 'INSERT', colval_l_ins)
  371. ## @brief Create trigger for a table
  372. #
  373. # Primarly designed to create trigger for DATETIME types
  374. # The method generates triggers of the form
  375. #
  376. # CREATE TRIGGER BEFORE <moment> ON <table_name>
  377. # FOR EACH ROW SET <for colname, colval in cols_val>
  378. # NEW.<colname> = <colval>,
  379. # <endfor>;
  380. # @param table_name str : The table name
  381. # @param moment str : can be 'update' or 'insert'
  382. # @param cols_val dict : Dict with column name as key and column value as value
  383. def _table_trigger(self, table_name, moment, cols_val):
  384. trigger_name = utils.escape_idname("%s_%s_trig" % (table_name, moment))
  385. #Try to delete the trigger
  386. drop_trig = """DROP TRIGGER IF EXISTS {trigger_name};""".format(trigger_name=trigger_name)
  387. self._query(drop_trig)
  388. col_val_l = ', '.join(["NEW.%s = %s" % (utils.escape_idname(utils.column_name(cname)), cval)for cname, cval in cols_val.items()])
  389. #Create a trigger if needed
  390. if len(col_val_l) > 0:
  391. trig_q = """CREATE TRIGGER {trigger_name} BEFORE {moment} ON {table_name}
  392. FOR EACH ROW SET {col_val_list};""".format(
  393. trigger_name=trigger_name,
  394. table_name=utils.escape_idname(table_name),
  395. moment=moment, col_val_list=col_val_l
  396. )
  397. self._query(trig_q)
  398. ## @brief Identifier escaping
  399. # @param idname str : An SQL identifier
  400. #def _idname_escape(self, idname):
  401. # if '`' in idname:
  402. # raise ValueError("Invalid name : '%s'"%idname)
  403. # return '`%s`'%idname
  404. ## @brief Returns column specs from fieldtype
  405. # @param emfieldtype EmFieldType : An EmFieldType insance
  406. # @todo escape default value
  407. def _field_to_specs(self, emfieldtype):
  408. colspec = ''
  409. if not emfieldtype.nullable:
  410. colspec = 'NOT NULL'
  411. if hasattr(emfieldtype, 'default'):
  412. colspec += ' DEFAULT '
  413. if emfieldtype.default is None:
  414. colspec += 'NULL '
  415. else:
  416. colspec += emfieldtype.default # ESCAPE VALUE HERE !!!!
  417. if emfieldtype.name == 'pk':
  418. colspec += ' AUTO_INCREMENT'
  419. return colspec
  420. ## @brief Given a fieldtype return a MySQL type specifier
  421. # @param emfieldtype EmFieldType : A fieldtype
  422. # @return the corresponding MySQL type
  423. def _field_to_type(self, emfieldtype):
  424. ftype = emfieldtype.ftype
  425. if ftype == 'char' or ftype == 'str':
  426. res = "VARCHAR(%d)" % emfieldtype.max_length
  427. elif ftype == 'text':
  428. res = "TEXT"
  429. elif ftype == 'datetime':
  430. res = "DATETIME"
  431. # client side workaround for only one column with CURRENT_TIMESTAMP : giving NULL to timestamp that don't allows NULL
  432. # cf. https://dev.mysql.com/doc/refman/5.0/en/timestamp-initialization.html#idm139961275230400
  433. # The solution for the migration handler is to create triggers :
  434. # CREATE TRIGGER trigger_name BEFORE INSERT ON `my_super_table`
  435. # FOR EACH ROW SET NEW.my_date_column = NOW();
  436. # and
  437. # CREATE TRIGGER trigger_name BEFORE UPDATE ON
  438. elif ftype == 'bool':
  439. res = "BOOL"
  440. elif ftype == 'int':
  441. res = "INT"
  442. elif ftype == 'rel2type':
  443. res = "INT"
  444. elif ftype == 'leobject':
  445. res = "INT"
  446. else:
  447. raise ValueError("Unsuported fieldtype ftype : %s" % ftype)
  448. return res
  449. ## @brief Delete all table created by the MH
  450. # @param model Model : the Editorial model
  451. def __purge_db(self, model):
  452. for uid in [c.uid for c in model.components('EmClass')]:
  453. try:
  454. self.delete_emclass_table(model, uid)
  455. except self._dbmodule.err.InternalError as expt:
  456. print(expt)
  457. for tname in [utils.r2t_table_name(f.em_class.name, model.component(f.rel_to_type_id).name) for f in model.components('EmField') if f.fieldtype == 'rel2type']:
  458. try:
  459. self._query("DROP TABLE %s;" % tname)
  460. except self._dbmodule.err.InternalError as expt:
  461. print(expt)
  462. for tname in [utils.common_tables['relation'], utils.common_tables['relation']]:
  463. try:
  464. self._query("DROP TABLE %s;" % tname)
  465. except self._dbmodule.err.InternalError as expt:
  466. print(expt)
  467. ## @brief Return primary key name & fieldtype for relation or object
  468. @classmethod
  469. def extract_pk(cls, common_fields):
  470. for fname, finfo in common_fields.items():
  471. if finfo['fieldtype'] == 'pk':
  472. fto = EditorialModel.fieldtypes.generic.GenericFieldType.from_name('pk')
  473. finfo_cp = copy.copy(finfo)
  474. del(finfo_cp['fieldtype'])
  475. return (utils.column_name(fname), fto(**finfo_cp))
  476. raise RuntimeError("No primary key found in common fields : %s" % common_fields)
  477. ## @brief Exec a query
  478. # @param query str : SQL query
  479. def _query(self, query):
  480. if self.debug:
  481. print(query + "\n")
  482. if not self.dryrun:
  483. with self.db_conn.cursor() as cur:
  484. cur.execute(query)
  485. self.db_conn.commit() # autocommit
  486. ## @brief Given a common field name return an EmFieldType instance
  487. # @param cname str : Common field name
  488. # @return An EmFieldType instance
  489. @classmethod
  490. def _common_field_to_ftype(cls, cname):
  491. fta = copy.copy(EditorialModel.classtypes.common_fields[cname])
  492. fto = EditorialModel.fieldtypes.generic.GenericFieldType.from_name(fta['fieldtype'])
  493. del fta['fieldtype']
  494. return fto(**fta)
  495. ## @brief Returns a tuple (pkname, pk_ftype)
  496. @property
  497. def _object_pk(self):
  498. return self.extract_pk(EditorialModel.classtypes.common_fields)
  499. ## @brief Returns a tuple (rel_pkname, rel_ftype)
  500. @property
  501. def _relation_pk(self):
  502. return self.extract_pk(EditorialModel.classtypes.relations_common_fields)
  503. ## @brief Returns a dict { colname:fieldtype } of relation table columns
  504. @property
  505. def _relation_cols(self):
  506. res = dict()
  507. for fieldname, fieldinfo in EditorialModel.classtypes.relations_common_fields.items():
  508. finfo = copy.copy(fieldinfo)
  509. fieldtype_name = finfo['fieldtype']
  510. del(finfo['fieldtype'])
  511. res[fieldname] = EditorialModel.fieldtypes.generic.GenericFieldType.from_name(fieldtype_name)(**finfo)
  512. return res