apb_extra_utils.ddl_to_scd.ddl_to_scd
1# coding=utf-8 2# 3# Author: Ernesto Arredondo Martinez (ernestone@gmail.com) 4# Created: 7/6/19 18:23 5# Last modified: 7/6/19 18:21 6# Copyright (c) 2019 7 8import os 9import re 10import sys 11 12from ..sql_parser import x_sql_parser as SqlParser 13 14rex_ini_pal = r"(^|,|\(|\s|\"|')" 15rex_fin_pal = r"($|,|\)|\s|\"|')" 16 17 18def val_in_txt(search_val, a_txt): 19 """ 20 Busca valor (search_val) en texto (a_txt) ignorando mayusculas 21 Args: 22 search_val: 23 a_txt: 24 25 Returns: 26 re.search 27 """ 28 return re.search(search_val, a_txt, re.IGNORECASE) 29 30 31def palabra_in_txt(palabra, a_txt): 32 """ 33 Busca palabra (search_val) en texto (a_txt) ignorando mayusculas 34 35 Args: 36 palabra: 37 a_txt: 38 39 Returns: 40 re.search 41 """ 42 return re.search(rex_ini_pal + palabra + rex_fin_pal, 43 a_txt, re.IGNORECASE) 44 45 46class xDdlParser(SqlParser.xSqlParser): 47 """ 48 Clase que parsea elementos de DDL para definición de tablas 49 """ 50 __slots__ = ('a_sql_parser', 'stmnt_create_tab', 'nom_tab', 'stmnt_grup_def_camps') 51 52 sql_pk = "PRIMARY KEY" 53 SEP = SqlParser.SEP 54 tokPunctuation = SqlParser.toks.Punctuation 55 ApbElemStmnt = SqlParser.xElemStmntSql 56 SqlParser = SqlParser.xSqlParser 57 58 def __init__(self, a_sql_text): 59 """ 60 Inicializa el parseador a partir texto sql de una DDL de tabla 61 Args: 62 a_sql_text: 63 """ 64 super(xDdlParser, self).__init__(a_sql_text) 65 66 self.stmnt_create_tab = self.get_stmnt("CREATE TABLE") 67 if self.stmnt_create_tab is None: 68 raise NameError("ERROR: El texto sql no tiene sentencia 'CREATE TABLE'") 69 70 self.nom_tab = self.stmnt_create_tab.id_principal 71 72 idx_id_nom_tab = self.stmnt_create_tab.get_elem_match_val(self.nom_tab).index 73 self.stmnt_grup_def_camps = self.stmnt_create_tab.get_elem_post(idx_id_nom_tab).elem 74 # Se quitan los posibles campos con '?" 75 self.stmnt_grup_def_camps.substitute_val(r"\?", "") 76 77 # Se añaden campos definidos en sentencias 'ALTER TABLE nom_tab ADD (def_camp)' 78 rex_add_col = re.compile(r"ALTER TABLE [']?" + self.nom_tab + "[']? ADD [(]", re.IGNORECASE) 79 for a_stmnt in self.others_apb_statements(): 80 if rex_add_col.match(a_stmnt.format_val): 81 for elem_def_camp in filter(lambda el: el.tipo != self.SEP, 82 a_stmnt.get_next_elem_for_key("ADD").elem.elems_stmnt_sin_esp(1, -1)): 83 self.add_def_campo(elem_def_camp.format_val) 84 85 @property 86 def iter_elems_key_camps(self): 87 for a_stmnt in self.x_statements: 88 elem_pos_pk = a_stmnt.get_elem_match_val(self.sql_pk) 89 if elem_pos_pk is not None: 90 stmnt_grup_camps_key = elem_pos_pk.elem.parent_stmnt.get_elem_post(elem_pos_pk.index).elem 91 92 if stmnt_grup_camps_key.is_group: 93 # Sin los parentesis y sin las comas 94 for el in filter(lambda el: el.tipo != self.SEP, 95 stmnt_grup_camps_key.elems_stmnt_sin_esp(1, -1)): 96 yield el 97 98 @property 99 def list_key_camps(self): 100 return [el.format_val.replace("'", "").replace('"', "") for el in self.iter_elems_key_camps] 101 102 @property 103 def iter_elems_def_camp(self): 104 for el in self.stmnt_grup_def_camps.elems_stmnt[1:-1]: 105 if el.tipo == self.SEP or palabra_in_txt(self.sql_pk, el.format_val): 106 continue 107 108 nom_camp = el.format_val.split(" ")[0].replace("'", "").replace('"', "") 109 yield nom_camp, el 110 111 @property 112 def list_def_camps(self): 113 return [el.format_val for nom_camp, el in self.iter_elems_def_camp] 114 115 def add_def_campo(self, str_def_camp): 116 """ 117 Añade definición de campo 118 119 Args: 120 str_def_camp: 121 """ 122 # Si se está añadiendo una Constraint entonces NO se trata como definicion de campo 123 if re.search(r"CONSTRAINT", str_def_camp, re.IGNORECASE): 124 return 125 126 elem_sep = self.ApbElemStmnt(None, self.tokPunctuation, ",") 127 self.stmnt_grup_def_camps.add_token_as_x_elem(elem_sep, -1) 128 129 a_parser_camp = self.SqlParser("(" + str_def_camp + ")") 130 a_def_camp_stmnt = a_parser_camp.x_statements[0].elems_stmnt[0].elems_stmnt[1] 131 132 self.stmnt_grup_def_camps.add_token_as_x_elem(a_def_camp_stmnt, -1) 133 134 def others_apb_statements(self): 135 for stmnt in self.x_statements: 136 if stmnt.format_val != "" and stmnt != self.stmnt_create_tab: 137 yield stmnt 138 139 140class ApbDdlToScd(object): 141 SCD4 = "SCD4" 142 SCD4c = "SCD4C" 143 SCD2 = "SCD2" 144 prefix_val_param = "A_" 145 sufix_taula_vers = "_VE" 146 sufix_seq = "_SEQ" 147 limit_noms_sql = 30 148 nom_prev_reg_vers = "PREV_REG_VERS" 149 nom_new_reg_vers = "NEW_REG_VERS" 150 nom_regaux_vers = "REGAUX" 151 nom_old_reg_vers = "OLD_REG" 152 prefix_delete_trigger = "DEL_" 153 prefix_insert_trigger = "INS_" 154 prefix_update_trigger = "UPD_" 155 prefix_primary_key = "PK_" 156 prefix_ins_upd_trigger = prefix_insert_trigger + prefix_update_trigger 157 prefix_index = "IDX_" 158 sufix_unic = "_UN" 159 sufix_trigger = "_TRG" 160 sufix_fk = "_FK" 161 sufix_check_constraint = "_CHK" 162 prefix_check_constraint = "CHK_" 163 default_date_version = "CURRENT_DATE" 164 templates_tipo = {SCD4: "template_base_SCD4.sql", 165 SCD4c: "template_base_SCD4c.sql", 166 SCD2: "template_base_SCD2.sql"} 167 168 __slots__ = ('ddl_parser', 169 'nom_taula', 170 'def_camps_taula', 171 'nom_camps_clau', 172 'sql_date_version') 173 174 def convert_ddl_file_to_scd(self, ddl_file, 175 list_extra_camps=None, 176 a_sql_date_version=None, 177 tipo_scd='SCD4'): 178 """ 179 Genera a partir de DDL de tabla de Oracle (ddl_file) un nuevo script sobre el subdirectorio 'ddl_[tipo_scd]' con el mismo nombre pero el sufijo '_VE', con los objectos SQL (create, triggers, index, ...) que permitirán la gestión de versiones de cada cambio sobre dicha tabla. 180 181 Args: 182 ddl_file: path del fichero DDL de la tabla Oracle que se quiere versionar 183 list_extra_camps: (OPC) lista con definicion de campos sql 184 a_sql_date_version: (OPC) senetencia sql que devolverá la fecha de versión. Por defecto 'CURRENT_DATE' 185 tipo_scd: (OPC) el tipo de SCD que se quiere crear. Opciones disponibles: SCD4 y SCD4C. Por defecto 'SCD4' (Slowly changing dimension 4). El tipo SCD4C añade a la tabla de versiones compound triggers de Oracle para mantener integridad fechas sin incurrir en error 'mutating tables' 186 187 Returns: 188 path_file_scd (string) 189 """ 190 191 ok = self._set_parser_ddl_file(ddl_file, list_extra_camps) 192 if ok: 193 self.sql_date_version = self.default_date_version 194 if a_sql_date_version: 195 self.sql_date_version = a_sql_date_version 196 197 return self.create_ddl_scd_file(ddl_file, tipo_scd) 198 199 def _set_parser_ddl_file(self, ddl_file, list_extra_camps=None): 200 """ 201 202 Args: 203 ddl_file: 204 list_extra_camps: 205 206 Returns: 207 ok (boolean) 208 """ 209 self.ddl_parser = None 210 ok = False 211 212 try: 213 with open(ddl_file, encoding='utf8') as a_file: 214 a_sql_text = a_file.read() 215 216 self.ddl_parser = xDdlParser(a_sql_text) 217 218 if list_extra_camps is not None: 219 for extra_def_camp in list_extra_camps: 220 self.ddl_parser.add_def_campo(extra_def_camp) 221 222 self._set_dades_taula() 223 224 ok = True 225 except NameError: 226 print("El fichero '" + ddl_file + "' no tiene sentencia 'CREATE TABLE'") 227 228 return ok 229 230 def _set_dades_taula(self): 231 """ 232 Asigna datos de la tabla 233 """ 234 self.nom_taula = self.format_nom_ddl(self.ddl_parser.nom_tab) 235 236 # Per si hi ha algun camp que és diu igual que els camps de DATA_INI i FI 237 self.ddl_parser.stmnt_grup_def_camps.substitute_val(self.nom_datini, self.nom_datini + "_BASE") 238 self.ddl_parser.stmnt_grup_def_camps.substitute_val(self.nom_datfi, self.nom_datfi + "_BASE") 239 240 self.def_camps_taula = {} 241 for nom_camp, elem_camp in self.ddl_parser.iter_elems_def_camp: 242 def_camp = elem_camp.format_val 243 vals_def_camp = def_camp.split(' ') 244 self.def_camps_taula[self.format_nom_ddl(nom_camp)] = " ".join(vals_def_camp[1:]) 245 246 self.nom_camps_clau = self.ddl_parser.list_key_camps 247 248 def create_ddl_scd_file(self, ddl_file, tipo_scd): 249 """ 250 Crear fichero DDL SCD según tipo 251 252 Args: 253 ddl_file: 254 tipo_scd: 255 256 Returns: 257 ddl_file_scd (string): path file SCD 258 """ 259 parts_file = os.path.splitext(os.path.basename(ddl_file)) 260 dir_ver = os.path.join(os.path.dirname(ddl_file), "ddls_" + tipo_scd) 261 ddl_file_ver = os.path.join(dir_ver, parts_file[0] + "_VE" + parts_file[1]) 262 263 if not os.path.exists(dir_ver): 264 os.makedirs(dir_ver) 265 266 mode_io = "w" 267 if not os.path.exists(ddl_file_ver): 268 mode_io = "x" 269 270 with open(ddl_file_ver, mode_io, encoding='utf-8') as a_file: 271 a_file.write(self.ddl_parser.as_script_sql) 272 a_file.write("\n") 273 a_file.write(self.get_ddl_scd(tipo_scd=tipo_scd)) 274 275 # Se añade los sql_statements al DDL de la tabla versionada que no hagan referencia a 'ALTER TABLE tab ADD' 276 rex_alter_add = re.compile(r"ALTER TABLE [']?" + self.nom_taula + "[']? ADD", re.IGNORECASE) 277 rex_nom_tab_ve = re.compile(r"[\w'\"]*" + self.nom_taula_vers + r"[\w'\"]*", re.IGNORECASE) 278 for a_stmnt in self.ddl_parser.others_apb_statements(): 279 if rex_alter_add.match(a_stmnt.format_val): 280 continue 281 282 a_elem_pos_stmnt = a_stmnt.get_elem_match_val(rex_ini_pal + self.nom_taula + rex_fin_pal) 283 if a_elem_pos_stmnt: 284 a_elem_pos_stmnt.elem.substitute_val(self.nom_taula, self.nom_taula_vers) 285 286 id_stmnt = a_stmnt.id_principal 287 if id_stmnt is not None and not rex_nom_tab_ve.match(id_stmnt): 288 a_stmnt.substitute_val(id_stmnt, 289 self.get_nom_obj_sql(id_stmnt, sufix=self.sufix_taula_vers)) 290 291 a_file.write("\n\n") 292 a_file.write(a_stmnt.format_val) 293 a_file.write("\n") 294 a_file.write("/") 295 a_file.write("\n") 296 297 return ddl_file_ver 298 299 def get_ddl_tmpl(self, tipo_scd=SCD4): 300 """ 301 Retorna template a usar según tipo_scd 302 303 Args: 304 tipo_scd: 305 306 Returns: 307 template_scd (string): path del template a utilizar 308 """ 309 a_template_scd = "" 310 tipo_scd = tipo_scd.upper() 311 312 if tipo_scd == self.SCD4c: 313 with open(os.path.join(os.path.dirname(__file__), self.templates_tipo[self.SCD4]), 314 encoding='utf8') as a_file: 315 a_template_scd += a_file.read() 316 a_template_scd += "\n" 317 318 with open(os.path.join(os.path.dirname(__file__), self.templates_tipo[tipo_scd]), 319 encoding='utf8') as a_file: 320 a_template_scd += a_file.read() 321 322 return a_template_scd 323 324 def get_ddl_scd(self, tipo_scd=SCD4): 325 """ 326 Retorna ddl SCD base 327 Args: 328 tipo_scd: 329 330 Returns: 331 a_ddl_scd (string): path ddl scd base 332 """ 333 a_ddl_scd = self.get_ddl_tmpl(tipo_scd).format(self=self) 334 335 return a_ddl_scd 336 337 def add_property_func(self, nom_prop, a_func): 338 """ 339 Añade funcion a self.__class__ para poder usar templates personalizados 340 341 Args: 342 nom_prop: 343 a_func: 344 """ 345 setattr(self.__class__, nom_prop, property(a_func)) 346 347 @staticmethod 348 def format_nom_ddl(nom_ddl): 349 """ 350 Para cualquier string que reciba como nombre de elemento en DDL lo formatea a mayúsculas y sin " 351 Args: 352 nom_ddl: 353 354 Returns: 355 string 356 """ 357 # 358 return nom_ddl.strip().replace('"', '').upper() 359 360 def clau_as_def_params_func(self, prefix_param_templ=prefix_val_param, def_param=False): 361 """ 362 Retorna clave tabla como parametros de funcion de PL/SQL 363 Args: 364 prefix_param_templ: 365 def_param: 366 367 Returns: 368 string 369 """ 370 sufix_param_templ = "" 371 if def_param: 372 sufix_param_templ = " " + self.nom_taula_vers + ".{nom_camp_clau}%TYPE" 373 374 param_templ = prefix_param_templ + "{nom_camp_clau}" + sufix_param_templ 375 claus_as_params = [] 376 for nom_camp in self.nom_camps_clau: 377 claus_as_params.append(param_templ.format(nom_camp_clau=nom_camp)) 378 379 return ", ".join(claus_as_params) 380 381 def sql_query_eq_clau(self, prefix_param=prefix_val_param): 382 """ 383 Retorna clave tabla como query SQL 384 Args: 385 prefix_param: 386 387 Returns: 388 string 389 """ 390 prefix_param_templ = "" 391 if prefix_param is not None: 392 prefix_param_templ = prefix_param 393 394 query_eq_clau_templ = self.nom_taula_vers + ".{nom_clau} = " + prefix_param_templ + "{nom_clau}" 395 list_query_claus = [] 396 for nom_camp in self.nom_camps_clau: 397 list_query_claus.append(query_eq_clau_templ.format(nom_clau=nom_camp)) 398 399 return " AND ".join(list_query_claus) 400 401 def def_set_camps_clau(self, 402 oper_set="=", 403 sep_set_camp=", ", 404 prefix_camp="", 405 prefix_val=""): 406 """ 407 Retorna SQL para hacer SET de valores de los campos clave 408 Args: 409 oper_set: 410 sep_set_camp: 411 prefix_camp: 412 prefix_val: 413 414 Returns: 415 string 416 """ 417 if len(prefix_camp) != 0: 418 prefix_camp += "." 419 if len(prefix_val) != 0: 420 prefix_val += "." 421 422 set_camp_templ = prefix_camp + "{nom_camp} " + oper_set + " " + prefix_val + "{nom_camp}" 423 424 list_set_camps = [] 425 for a_nom_camp in self.nom_camps_clau: 426 list_set_camps.append(set_camp_templ.format(nom_camp=a_nom_camp)) 427 428 return sep_set_camp.join(list_set_camps) 429 430 def def_set_camps_taula(self, 431 oper_set="=", 432 sep_set_camp=", ", 433 prefix_camp="", 434 prefix_val="", 435 set_camps_clau=False): 436 """ 437 Retorna SQL para hacer SET de los valores de los campos 438 Args: 439 oper_set: 440 sep_set_camp: 441 prefix_camp: 442 prefix_val: 443 set_camps_clau: 444 445 Returns: 446 string 447 """ 448 if len(prefix_camp) != 0: 449 prefix_camp += "." 450 if len(prefix_val) != 0: 451 prefix_val += "." 452 453 set_camp_templ = prefix_camp + "{nom_camp} " + oper_set + " " + prefix_val + "{nom_camp}" 454 455 list_set_camps = [] 456 for a_nom_camp in self.def_camps_taula.keys(): 457 if not set_camps_clau and a_nom_camp in self.nom_camps_clau: 458 continue 459 460 list_set_camps.append(set_camp_templ.format(nom_camp=a_nom_camp)) 461 462 return sep_set_camp.join(list_set_camps) 463 464 def get_nom_obj_sql(self, nom_base, prefix="", sufix=""): 465 """ 466 Devuelve nombre objecto SQL con prefijo y sufijo ajustado a la longitud máxima de nombres en PL/SQL 467 Args: 468 nom_base: 469 prefix: 470 sufix: 471 472 Returns: 473 string 474 """ 475 return SqlParser.get_nom_obj_sql(nom_base, prefix, sufix) 476 477 @property 478 def date_version(self): 479 return self.sql_date_version 480 481 def camps_clau(self): 482 for nom_camp in self.nom_camps_clau: 483 yield "{} {}".format(nom_camp, self.def_camps_taula.get(nom_camp)) 484 485 @property 486 def def_camps_clau(self): 487 return ", ".join(self.camps_clau()) 488 489 def camps_dades(self): 490 for nom_camp in sorted(self.def_camps_taula): 491 if nom_camp not in self.nom_camps_clau: 492 yield "{} {}".format(nom_camp, self.def_camps_taula.get(nom_camp)) 493 494 @property 495 def def_camps_dades(self): 496 return ", ".join(self.camps_dades()) 497 498 @property 499 def alter_camps_dades_taula_orig(self): 500 return "\n".join(["ALTER TABLE {nom_taula} ADD ({def_col});".format(nom_taula=self.nom_taula, 501 def_col=def_col) 502 for def_col in self.camps_dades()]) 503 504 @property 505 def alter_camps_dades_taula_vers(self): 506 return "\n".join(["ALTER TABLE {nom_taula} ADD ({def_col});".format(nom_taula=self.nom_taula_vers, 507 def_col=def_col) 508 for def_col in self.camps_dades()]) 509 510 @property 511 def str_camps_clau(self): 512 return ", ".join(self.nom_camps_clau) 513 514 @property 515 def primer_camp_clau(self): 516 return self.nom_camps_clau[0] 517 518 @property 519 def nom_taula_vers(self): 520 return self.get_nom_obj_sql(self.nom_taula, sufix=self.sufix_taula_vers) 521 522 @property 523 def nom_seq_vers(self): 524 return self.get_nom_obj_sql(self.nom_taula, sufix=self.sufix_taula_vers + self.sufix_seq) 525 526 @property 527 def def_cursor_prev_date_reg_vers(self): 528 def_curs_templ = "({params_clau}, A_DAT_VER DATE) IS " \ 529 "SELECT * FROM " + self.nom_taula_vers + \ 530 " WHERE {query_clau} AND " + \ 531 self.nom_taula_vers + "." + self.nom_datini + \ 532 " <= A_DAT_VER ORDER BY " + self.nom_datini + " DESC" 533 534 return def_curs_templ.format(params_clau=self.clau_as_def_params_func(def_param=True), 535 query_clau=self.sql_query_eq_clau()) 536 537 @property 538 def def_cursor_actual_reg_vers(self): 539 def_curs_templ = "({params_clau}) IS " \ 540 "SELECT * FROM " + self.nom_taula_vers + \ 541 " WHERE {query_clau} AND " + \ 542 self.nom_taula_vers + "." + self.nom_datfi + \ 543 " IS NULL ORDER BY " + self.nom_datini + " DESC" 544 545 return def_curs_templ.format(params_clau=self.clau_as_def_params_func(def_param=True), 546 query_clau=self.sql_query_eq_clau()) 547 548 @property 549 def params_cursor_clau_reg_new(self): 550 return self.clau_as_def_params_func(prefix_param_templ=":NEW.") 551 552 @property 553 def params_cursor_clau_reg_old(self): 554 return self.clau_as_def_params_func(prefix_param_templ=":OLD.") 555 556 @property 557 def sql_query_eq_clau_for_regaux(self): 558 return self.sql_query_eq_clau(prefix_param=(self.nom_regaux_vers + ".")) 559 560 @property 561 def sql_query_eq_clau_for_new_reg(self): 562 return self.sql_query_eq_clau(prefix_param=":NEW.") 563 564 @property 565 def sql_query_eq_clau_for_prev_reg(self): 566 return self.sql_query_eq_clau(prefix_param=(self.nom_prev_reg_vers + ".")) 567 568 @property 569 def sql_query_eq_clau_for_old_reg(self): 570 return self.sql_query_eq_clau(prefix_param=(self.nom_old_reg_vers + ".")) 571 572 @property 573 def set_camps_new_reg_ver(self): 574 return self.def_set_camps_taula(oper_set=":=", 575 sep_set_camp=";\n", 576 prefix_camp=self.nom_new_reg_vers, 577 prefix_val=":NEW", 578 set_camps_clau=True) 579 580 @property 581 def set_camps_update_reg_ver(self): 582 return self.def_set_camps_taula(prefix_val=":NEW") 583 584 @property 585 def nom_trigger_del_tab_base(self): 586 return self.get_nom_obj_sql(self.nom_taula, 587 self.prefix_delete_trigger, 588 self.sufix_trigger) 589 590 @property 591 def nom_trigger_ins_upd_tab_base(self): 592 return self.get_nom_obj_sql(self.nom_taula, 593 self.prefix_ins_upd_trigger, 594 self.sufix_trigger) 595 596 @property 597 def nom_trigger_del_tab_vers(self): 598 return self.get_nom_obj_sql(self.nom_taula, 599 self.prefix_delete_trigger, 600 self.sufix_taula_vers + self.sufix_trigger) 601 602 @property 603 def nom_trigger_ins_tab_vers(self): 604 return self.get_nom_obj_sql(self.nom_taula, 605 self.prefix_insert_trigger, 606 self.sufix_taula_vers + self.sufix_trigger) 607 608 @property 609 def nom_trigger_upd_tab_vers(self): 610 return self.get_nom_obj_sql(self.nom_taula, 611 self.prefix_update_trigger, 612 self.sufix_taula_vers + self.sufix_trigger) 613 614 @property 615 def nom_constraint_chk_vers(self): 616 return self.get_nom_obj_sql(self.nom_taula, 617 sufix=self.sufix_check_constraint) 618 619 @property 620 def nom_var_prev_reg_vers(self): 621 return self.nom_prev_reg_vers 622 623 @property 624 def nom_var_new_reg_vers(self): 625 return self.nom_new_reg_vers 626 627 @property 628 def nom_var_regaux_vers(self): 629 return self.nom_regaux_vers 630 631 @property 632 def set_camps_clau_regaux_from_new(self): 633 return self.def_set_camps_clau(oper_set=":=", 634 sep_set_camp=";", 635 prefix_camp=self.nom_regaux_vers, 636 prefix_val=":NEW") 637 638 @property 639 def set_camps_clau_regaux_from_old(self): 640 return self.def_set_camps_clau(oper_set=":=", 641 sep_set_camp=";", 642 prefix_camp=self.nom_regaux_vers, 643 prefix_val=":OLD") 644 645 @property 646 def nom_var_old_reg_vers(self): 647 return self.nom_old_reg_vers 648 649 @property 650 def set_camps_oldreg_from_old(self): 651 return self.def_set_camps_taula(oper_set=":=", 652 sep_set_camp=";\n", 653 prefix_camp=self.nom_old_reg_vers, 654 prefix_val=":OLD", 655 set_camps_clau=True) 656 657 @property 658 def nom_datini(self): 659 return "DAT_INI_VER" 660 661 @property 662 def nom_datfi(self): 663 return "DAT_FI_VER" 664 665 @property 666 def nom_idx_datini(self): 667 return self.get_nom_obj_sql(self.nom_taula, self.prefix_index, self.sufix_taula_vers + "_" + self.nom_datini) 668 669 @property 670 def nom_idx_datfi(self): 671 return self.get_nom_obj_sql(self.nom_taula, self.prefix_index, self.sufix_taula_vers + "_" + self.nom_datfi) 672 673 @property 674 def nom_taula_vers_pk(self): 675 return self.get_nom_obj_sql(self.nom_taula, self.prefix_primary_key, self.sufix_taula_vers) 676 677 @property 678 def idx_nom_taula_vers_pk_datini(self): 679 return self.get_nom_obj_sql(self.nom_taula, "PKDAT_", self.sufix_taula_vers + self.sufix_unic) 680 681 @property 682 def nom_taula_vers_datini_chk(self): 683 return self.get_nom_obj_sql(self.nom_taula, "DAT_", self.sufix_taula_vers + self.sufix_check_constraint) 684 685 def get_triggers_extra_tab_base(self): 686 """ 687 (SUBCLASEAR) Retorna lista strings con las definiciones de triggers extra a incluir 688 """ 689 return [] 690 691 @property 692 def triggers_extra_tab_base(self): 693 str_ret = "" 694 l_trgs = self.get_triggers_extra_tab_base() 695 if l_trgs: 696 str_ret = "{a_sql}".format(a_sql="\n".join(l_trgs)) 697 698 return str_ret 699 700 def get_follows_trigger_ins_upd_tab_base(self): 701 """ 702 (SUBCLASEAR) Retorna lista de strings con los nombres de triggers que precederán al trigger de la tabla 703 base que se encargará del versionado (el del nombre dado por property 'self.nom_nom_trigger_ins_upd_tab_base') 704 """ 705 return [] 706 707 @property 708 def follows_trigger_ins_upd_tab_base(self): 709 str_ret = "" 710 l_trgs = self.get_follows_trigger_ins_upd_tab_base() 711 if l_trgs: 712 str_ret = "\nFOLLOWS {a_sql}".format(a_sql=", ".join(l_trgs)) 713 714 return str_ret 715 716 717if __name__ == '__main__': 718 a_eng = ApbDdlToScd() 719 720 import fire 721 path_scd = fire.Fire(a_eng.convert_ddl_file_to_scd) 722 723 ret = 1 724 if path_scd: 725 print("Fichero '{path_scd}' creado con éxito".format(path_scd=path_scd)) 726 ret = 0 727 728 sys.exit(ret)
19def val_in_txt(search_val, a_txt): 20 """ 21 Busca valor (search_val) en texto (a_txt) ignorando mayusculas 22 Args: 23 search_val: 24 a_txt: 25 26 Returns: 27 re.search 28 """ 29 return re.search(search_val, a_txt, re.IGNORECASE)
Busca valor (search_val) en texto (a_txt) ignorando mayusculas
Arguments:
- search_val:
- a_txt:
Returns:
re.search
32def palabra_in_txt(palabra, a_txt): 33 """ 34 Busca palabra (search_val) en texto (a_txt) ignorando mayusculas 35 36 Args: 37 palabra: 38 a_txt: 39 40 Returns: 41 re.search 42 """ 43 return re.search(rex_ini_pal + palabra + rex_fin_pal, 44 a_txt, re.IGNORECASE)
Busca palabra (search_val) en texto (a_txt) ignorando mayusculas
Arguments:
- palabra:
- a_txt:
Returns:
re.search
47class xDdlParser(SqlParser.xSqlParser): 48 """ 49 Clase que parsea elementos de DDL para definición de tablas 50 """ 51 __slots__ = ('a_sql_parser', 'stmnt_create_tab', 'nom_tab', 'stmnt_grup_def_camps') 52 53 sql_pk = "PRIMARY KEY" 54 SEP = SqlParser.SEP 55 tokPunctuation = SqlParser.toks.Punctuation 56 ApbElemStmnt = SqlParser.xElemStmntSql 57 SqlParser = SqlParser.xSqlParser 58 59 def __init__(self, a_sql_text): 60 """ 61 Inicializa el parseador a partir texto sql de una DDL de tabla 62 Args: 63 a_sql_text: 64 """ 65 super(xDdlParser, self).__init__(a_sql_text) 66 67 self.stmnt_create_tab = self.get_stmnt("CREATE TABLE") 68 if self.stmnt_create_tab is None: 69 raise NameError("ERROR: El texto sql no tiene sentencia 'CREATE TABLE'") 70 71 self.nom_tab = self.stmnt_create_tab.id_principal 72 73 idx_id_nom_tab = self.stmnt_create_tab.get_elem_match_val(self.nom_tab).index 74 self.stmnt_grup_def_camps = self.stmnt_create_tab.get_elem_post(idx_id_nom_tab).elem 75 # Se quitan los posibles campos con '?" 76 self.stmnt_grup_def_camps.substitute_val(r"\?", "") 77 78 # Se añaden campos definidos en sentencias 'ALTER TABLE nom_tab ADD (def_camp)' 79 rex_add_col = re.compile(r"ALTER TABLE [']?" + self.nom_tab + "[']? ADD [(]", re.IGNORECASE) 80 for a_stmnt in self.others_apb_statements(): 81 if rex_add_col.match(a_stmnt.format_val): 82 for elem_def_camp in filter(lambda el: el.tipo != self.SEP, 83 a_stmnt.get_next_elem_for_key("ADD").elem.elems_stmnt_sin_esp(1, -1)): 84 self.add_def_campo(elem_def_camp.format_val) 85 86 @property 87 def iter_elems_key_camps(self): 88 for a_stmnt in self.x_statements: 89 elem_pos_pk = a_stmnt.get_elem_match_val(self.sql_pk) 90 if elem_pos_pk is not None: 91 stmnt_grup_camps_key = elem_pos_pk.elem.parent_stmnt.get_elem_post(elem_pos_pk.index).elem 92 93 if stmnt_grup_camps_key.is_group: 94 # Sin los parentesis y sin las comas 95 for el in filter(lambda el: el.tipo != self.SEP, 96 stmnt_grup_camps_key.elems_stmnt_sin_esp(1, -1)): 97 yield el 98 99 @property 100 def list_key_camps(self): 101 return [el.format_val.replace("'", "").replace('"', "") for el in self.iter_elems_key_camps] 102 103 @property 104 def iter_elems_def_camp(self): 105 for el in self.stmnt_grup_def_camps.elems_stmnt[1:-1]: 106 if el.tipo == self.SEP or palabra_in_txt(self.sql_pk, el.format_val): 107 continue 108 109 nom_camp = el.format_val.split(" ")[0].replace("'", "").replace('"', "") 110 yield nom_camp, el 111 112 @property 113 def list_def_camps(self): 114 return [el.format_val for nom_camp, el in self.iter_elems_def_camp] 115 116 def add_def_campo(self, str_def_camp): 117 """ 118 Añade definición de campo 119 120 Args: 121 str_def_camp: 122 """ 123 # Si se está añadiendo una Constraint entonces NO se trata como definicion de campo 124 if re.search(r"CONSTRAINT", str_def_camp, re.IGNORECASE): 125 return 126 127 elem_sep = self.ApbElemStmnt(None, self.tokPunctuation, ",") 128 self.stmnt_grup_def_camps.add_token_as_x_elem(elem_sep, -1) 129 130 a_parser_camp = self.SqlParser("(" + str_def_camp + ")") 131 a_def_camp_stmnt = a_parser_camp.x_statements[0].elems_stmnt[0].elems_stmnt[1] 132 133 self.stmnt_grup_def_camps.add_token_as_x_elem(a_def_camp_stmnt, -1) 134 135 def others_apb_statements(self): 136 for stmnt in self.x_statements: 137 if stmnt.format_val != "" and stmnt != self.stmnt_create_tab: 138 yield stmnt
Clase que parsea elementos de DDL para definición de tablas
59 def __init__(self, a_sql_text): 60 """ 61 Inicializa el parseador a partir texto sql de una DDL de tabla 62 Args: 63 a_sql_text: 64 """ 65 super(xDdlParser, self).__init__(a_sql_text) 66 67 self.stmnt_create_tab = self.get_stmnt("CREATE TABLE") 68 if self.stmnt_create_tab is None: 69 raise NameError("ERROR: El texto sql no tiene sentencia 'CREATE TABLE'") 70 71 self.nom_tab = self.stmnt_create_tab.id_principal 72 73 idx_id_nom_tab = self.stmnt_create_tab.get_elem_match_val(self.nom_tab).index 74 self.stmnt_grup_def_camps = self.stmnt_create_tab.get_elem_post(idx_id_nom_tab).elem 75 # Se quitan los posibles campos con '?" 76 self.stmnt_grup_def_camps.substitute_val(r"\?", "") 77 78 # Se añaden campos definidos en sentencias 'ALTER TABLE nom_tab ADD (def_camp)' 79 rex_add_col = re.compile(r"ALTER TABLE [']?" + self.nom_tab + "[']? ADD [(]", re.IGNORECASE) 80 for a_stmnt in self.others_apb_statements(): 81 if rex_add_col.match(a_stmnt.format_val): 82 for elem_def_camp in filter(lambda el: el.tipo != self.SEP, 83 a_stmnt.get_next_elem_for_key("ADD").elem.elems_stmnt_sin_esp(1, -1)): 84 self.add_def_campo(elem_def_camp.format_val)
Inicializa el parseador a partir texto sql de una DDL de tabla
Arguments:
- a_sql_text:
86 @property 87 def iter_elems_key_camps(self): 88 for a_stmnt in self.x_statements: 89 elem_pos_pk = a_stmnt.get_elem_match_val(self.sql_pk) 90 if elem_pos_pk is not None: 91 stmnt_grup_camps_key = elem_pos_pk.elem.parent_stmnt.get_elem_post(elem_pos_pk.index).elem 92 93 if stmnt_grup_camps_key.is_group: 94 # Sin los parentesis y sin las comas 95 for el in filter(lambda el: el.tipo != self.SEP, 96 stmnt_grup_camps_key.elems_stmnt_sin_esp(1, -1)): 97 yield el
116 def add_def_campo(self, str_def_camp): 117 """ 118 Añade definición de campo 119 120 Args: 121 str_def_camp: 122 """ 123 # Si se está añadiendo una Constraint entonces NO se trata como definicion de campo 124 if re.search(r"CONSTRAINT", str_def_camp, re.IGNORECASE): 125 return 126 127 elem_sep = self.ApbElemStmnt(None, self.tokPunctuation, ",") 128 self.stmnt_grup_def_camps.add_token_as_x_elem(elem_sep, -1) 129 130 a_parser_camp = self.SqlParser("(" + str_def_camp + ")") 131 a_def_camp_stmnt = a_parser_camp.x_statements[0].elems_stmnt[0].elems_stmnt[1] 132 133 self.stmnt_grup_def_camps.add_token_as_x_elem(a_def_camp_stmnt, -1)
Añade definición de campo
Arguments:
- str_def_camp:
141class ApbDdlToScd(object): 142 SCD4 = "SCD4" 143 SCD4c = "SCD4C" 144 SCD2 = "SCD2" 145 prefix_val_param = "A_" 146 sufix_taula_vers = "_VE" 147 sufix_seq = "_SEQ" 148 limit_noms_sql = 30 149 nom_prev_reg_vers = "PREV_REG_VERS" 150 nom_new_reg_vers = "NEW_REG_VERS" 151 nom_regaux_vers = "REGAUX" 152 nom_old_reg_vers = "OLD_REG" 153 prefix_delete_trigger = "DEL_" 154 prefix_insert_trigger = "INS_" 155 prefix_update_trigger = "UPD_" 156 prefix_primary_key = "PK_" 157 prefix_ins_upd_trigger = prefix_insert_trigger + prefix_update_trigger 158 prefix_index = "IDX_" 159 sufix_unic = "_UN" 160 sufix_trigger = "_TRG" 161 sufix_fk = "_FK" 162 sufix_check_constraint = "_CHK" 163 prefix_check_constraint = "CHK_" 164 default_date_version = "CURRENT_DATE" 165 templates_tipo = {SCD4: "template_base_SCD4.sql", 166 SCD4c: "template_base_SCD4c.sql", 167 SCD2: "template_base_SCD2.sql"} 168 169 __slots__ = ('ddl_parser', 170 'nom_taula', 171 'def_camps_taula', 172 'nom_camps_clau', 173 'sql_date_version') 174 175 def convert_ddl_file_to_scd(self, ddl_file, 176 list_extra_camps=None, 177 a_sql_date_version=None, 178 tipo_scd='SCD4'): 179 """ 180 Genera a partir de DDL de tabla de Oracle (ddl_file) un nuevo script sobre el subdirectorio 'ddl_[tipo_scd]' con el mismo nombre pero el sufijo '_VE', con los objectos SQL (create, triggers, index, ...) que permitirán la gestión de versiones de cada cambio sobre dicha tabla. 181 182 Args: 183 ddl_file: path del fichero DDL de la tabla Oracle que se quiere versionar 184 list_extra_camps: (OPC) lista con definicion de campos sql 185 a_sql_date_version: (OPC) senetencia sql que devolverá la fecha de versión. Por defecto 'CURRENT_DATE' 186 tipo_scd: (OPC) el tipo de SCD que se quiere crear. Opciones disponibles: SCD4 y SCD4C. Por defecto 'SCD4' (Slowly changing dimension 4). El tipo SCD4C añade a la tabla de versiones compound triggers de Oracle para mantener integridad fechas sin incurrir en error 'mutating tables' 187 188 Returns: 189 path_file_scd (string) 190 """ 191 192 ok = self._set_parser_ddl_file(ddl_file, list_extra_camps) 193 if ok: 194 self.sql_date_version = self.default_date_version 195 if a_sql_date_version: 196 self.sql_date_version = a_sql_date_version 197 198 return self.create_ddl_scd_file(ddl_file, tipo_scd) 199 200 def _set_parser_ddl_file(self, ddl_file, list_extra_camps=None): 201 """ 202 203 Args: 204 ddl_file: 205 list_extra_camps: 206 207 Returns: 208 ok (boolean) 209 """ 210 self.ddl_parser = None 211 ok = False 212 213 try: 214 with open(ddl_file, encoding='utf8') as a_file: 215 a_sql_text = a_file.read() 216 217 self.ddl_parser = xDdlParser(a_sql_text) 218 219 if list_extra_camps is not None: 220 for extra_def_camp in list_extra_camps: 221 self.ddl_parser.add_def_campo(extra_def_camp) 222 223 self._set_dades_taula() 224 225 ok = True 226 except NameError: 227 print("El fichero '" + ddl_file + "' no tiene sentencia 'CREATE TABLE'") 228 229 return ok 230 231 def _set_dades_taula(self): 232 """ 233 Asigna datos de la tabla 234 """ 235 self.nom_taula = self.format_nom_ddl(self.ddl_parser.nom_tab) 236 237 # Per si hi ha algun camp que és diu igual que els camps de DATA_INI i FI 238 self.ddl_parser.stmnt_grup_def_camps.substitute_val(self.nom_datini, self.nom_datini + "_BASE") 239 self.ddl_parser.stmnt_grup_def_camps.substitute_val(self.nom_datfi, self.nom_datfi + "_BASE") 240 241 self.def_camps_taula = {} 242 for nom_camp, elem_camp in self.ddl_parser.iter_elems_def_camp: 243 def_camp = elem_camp.format_val 244 vals_def_camp = def_camp.split(' ') 245 self.def_camps_taula[self.format_nom_ddl(nom_camp)] = " ".join(vals_def_camp[1:]) 246 247 self.nom_camps_clau = self.ddl_parser.list_key_camps 248 249 def create_ddl_scd_file(self, ddl_file, tipo_scd): 250 """ 251 Crear fichero DDL SCD según tipo 252 253 Args: 254 ddl_file: 255 tipo_scd: 256 257 Returns: 258 ddl_file_scd (string): path file SCD 259 """ 260 parts_file = os.path.splitext(os.path.basename(ddl_file)) 261 dir_ver = os.path.join(os.path.dirname(ddl_file), "ddls_" + tipo_scd) 262 ddl_file_ver = os.path.join(dir_ver, parts_file[0] + "_VE" + parts_file[1]) 263 264 if not os.path.exists(dir_ver): 265 os.makedirs(dir_ver) 266 267 mode_io = "w" 268 if not os.path.exists(ddl_file_ver): 269 mode_io = "x" 270 271 with open(ddl_file_ver, mode_io, encoding='utf-8') as a_file: 272 a_file.write(self.ddl_parser.as_script_sql) 273 a_file.write("\n") 274 a_file.write(self.get_ddl_scd(tipo_scd=tipo_scd)) 275 276 # Se añade los sql_statements al DDL de la tabla versionada que no hagan referencia a 'ALTER TABLE tab ADD' 277 rex_alter_add = re.compile(r"ALTER TABLE [']?" + self.nom_taula + "[']? ADD", re.IGNORECASE) 278 rex_nom_tab_ve = re.compile(r"[\w'\"]*" + self.nom_taula_vers + r"[\w'\"]*", re.IGNORECASE) 279 for a_stmnt in self.ddl_parser.others_apb_statements(): 280 if rex_alter_add.match(a_stmnt.format_val): 281 continue 282 283 a_elem_pos_stmnt = a_stmnt.get_elem_match_val(rex_ini_pal + self.nom_taula + rex_fin_pal) 284 if a_elem_pos_stmnt: 285 a_elem_pos_stmnt.elem.substitute_val(self.nom_taula, self.nom_taula_vers) 286 287 id_stmnt = a_stmnt.id_principal 288 if id_stmnt is not None and not rex_nom_tab_ve.match(id_stmnt): 289 a_stmnt.substitute_val(id_stmnt, 290 self.get_nom_obj_sql(id_stmnt, sufix=self.sufix_taula_vers)) 291 292 a_file.write("\n\n") 293 a_file.write(a_stmnt.format_val) 294 a_file.write("\n") 295 a_file.write("/") 296 a_file.write("\n") 297 298 return ddl_file_ver 299 300 def get_ddl_tmpl(self, tipo_scd=SCD4): 301 """ 302 Retorna template a usar según tipo_scd 303 304 Args: 305 tipo_scd: 306 307 Returns: 308 template_scd (string): path del template a utilizar 309 """ 310 a_template_scd = "" 311 tipo_scd = tipo_scd.upper() 312 313 if tipo_scd == self.SCD4c: 314 with open(os.path.join(os.path.dirname(__file__), self.templates_tipo[self.SCD4]), 315 encoding='utf8') as a_file: 316 a_template_scd += a_file.read() 317 a_template_scd += "\n" 318 319 with open(os.path.join(os.path.dirname(__file__), self.templates_tipo[tipo_scd]), 320 encoding='utf8') as a_file: 321 a_template_scd += a_file.read() 322 323 return a_template_scd 324 325 def get_ddl_scd(self, tipo_scd=SCD4): 326 """ 327 Retorna ddl SCD base 328 Args: 329 tipo_scd: 330 331 Returns: 332 a_ddl_scd (string): path ddl scd base 333 """ 334 a_ddl_scd = self.get_ddl_tmpl(tipo_scd).format(self=self) 335 336 return a_ddl_scd 337 338 def add_property_func(self, nom_prop, a_func): 339 """ 340 Añade funcion a self.__class__ para poder usar templates personalizados 341 342 Args: 343 nom_prop: 344 a_func: 345 """ 346 setattr(self.__class__, nom_prop, property(a_func)) 347 348 @staticmethod 349 def format_nom_ddl(nom_ddl): 350 """ 351 Para cualquier string que reciba como nombre de elemento en DDL lo formatea a mayúsculas y sin " 352 Args: 353 nom_ddl: 354 355 Returns: 356 string 357 """ 358 # 359 return nom_ddl.strip().replace('"', '').upper() 360 361 def clau_as_def_params_func(self, prefix_param_templ=prefix_val_param, def_param=False): 362 """ 363 Retorna clave tabla como parametros de funcion de PL/SQL 364 Args: 365 prefix_param_templ: 366 def_param: 367 368 Returns: 369 string 370 """ 371 sufix_param_templ = "" 372 if def_param: 373 sufix_param_templ = " " + self.nom_taula_vers + ".{nom_camp_clau}%TYPE" 374 375 param_templ = prefix_param_templ + "{nom_camp_clau}" + sufix_param_templ 376 claus_as_params = [] 377 for nom_camp in self.nom_camps_clau: 378 claus_as_params.append(param_templ.format(nom_camp_clau=nom_camp)) 379 380 return ", ".join(claus_as_params) 381 382 def sql_query_eq_clau(self, prefix_param=prefix_val_param): 383 """ 384 Retorna clave tabla como query SQL 385 Args: 386 prefix_param: 387 388 Returns: 389 string 390 """ 391 prefix_param_templ = "" 392 if prefix_param is not None: 393 prefix_param_templ = prefix_param 394 395 query_eq_clau_templ = self.nom_taula_vers + ".{nom_clau} = " + prefix_param_templ + "{nom_clau}" 396 list_query_claus = [] 397 for nom_camp in self.nom_camps_clau: 398 list_query_claus.append(query_eq_clau_templ.format(nom_clau=nom_camp)) 399 400 return " AND ".join(list_query_claus) 401 402 def def_set_camps_clau(self, 403 oper_set="=", 404 sep_set_camp=", ", 405 prefix_camp="", 406 prefix_val=""): 407 """ 408 Retorna SQL para hacer SET de valores de los campos clave 409 Args: 410 oper_set: 411 sep_set_camp: 412 prefix_camp: 413 prefix_val: 414 415 Returns: 416 string 417 """ 418 if len(prefix_camp) != 0: 419 prefix_camp += "." 420 if len(prefix_val) != 0: 421 prefix_val += "." 422 423 set_camp_templ = prefix_camp + "{nom_camp} " + oper_set + " " + prefix_val + "{nom_camp}" 424 425 list_set_camps = [] 426 for a_nom_camp in self.nom_camps_clau: 427 list_set_camps.append(set_camp_templ.format(nom_camp=a_nom_camp)) 428 429 return sep_set_camp.join(list_set_camps) 430 431 def def_set_camps_taula(self, 432 oper_set="=", 433 sep_set_camp=", ", 434 prefix_camp="", 435 prefix_val="", 436 set_camps_clau=False): 437 """ 438 Retorna SQL para hacer SET de los valores de los campos 439 Args: 440 oper_set: 441 sep_set_camp: 442 prefix_camp: 443 prefix_val: 444 set_camps_clau: 445 446 Returns: 447 string 448 """ 449 if len(prefix_camp) != 0: 450 prefix_camp += "." 451 if len(prefix_val) != 0: 452 prefix_val += "." 453 454 set_camp_templ = prefix_camp + "{nom_camp} " + oper_set + " " + prefix_val + "{nom_camp}" 455 456 list_set_camps = [] 457 for a_nom_camp in self.def_camps_taula.keys(): 458 if not set_camps_clau and a_nom_camp in self.nom_camps_clau: 459 continue 460 461 list_set_camps.append(set_camp_templ.format(nom_camp=a_nom_camp)) 462 463 return sep_set_camp.join(list_set_camps) 464 465 def get_nom_obj_sql(self, nom_base, prefix="", sufix=""): 466 """ 467 Devuelve nombre objecto SQL con prefijo y sufijo ajustado a la longitud máxima de nombres en PL/SQL 468 Args: 469 nom_base: 470 prefix: 471 sufix: 472 473 Returns: 474 string 475 """ 476 return SqlParser.get_nom_obj_sql(nom_base, prefix, sufix) 477 478 @property 479 def date_version(self): 480 return self.sql_date_version 481 482 def camps_clau(self): 483 for nom_camp in self.nom_camps_clau: 484 yield "{} {}".format(nom_camp, self.def_camps_taula.get(nom_camp)) 485 486 @property 487 def def_camps_clau(self): 488 return ", ".join(self.camps_clau()) 489 490 def camps_dades(self): 491 for nom_camp in sorted(self.def_camps_taula): 492 if nom_camp not in self.nom_camps_clau: 493 yield "{} {}".format(nom_camp, self.def_camps_taula.get(nom_camp)) 494 495 @property 496 def def_camps_dades(self): 497 return ", ".join(self.camps_dades()) 498 499 @property 500 def alter_camps_dades_taula_orig(self): 501 return "\n".join(["ALTER TABLE {nom_taula} ADD ({def_col});".format(nom_taula=self.nom_taula, 502 def_col=def_col) 503 for def_col in self.camps_dades()]) 504 505 @property 506 def alter_camps_dades_taula_vers(self): 507 return "\n".join(["ALTER TABLE {nom_taula} ADD ({def_col});".format(nom_taula=self.nom_taula_vers, 508 def_col=def_col) 509 for def_col in self.camps_dades()]) 510 511 @property 512 def str_camps_clau(self): 513 return ", ".join(self.nom_camps_clau) 514 515 @property 516 def primer_camp_clau(self): 517 return self.nom_camps_clau[0] 518 519 @property 520 def nom_taula_vers(self): 521 return self.get_nom_obj_sql(self.nom_taula, sufix=self.sufix_taula_vers) 522 523 @property 524 def nom_seq_vers(self): 525 return self.get_nom_obj_sql(self.nom_taula, sufix=self.sufix_taula_vers + self.sufix_seq) 526 527 @property 528 def def_cursor_prev_date_reg_vers(self): 529 def_curs_templ = "({params_clau}, A_DAT_VER DATE) IS " \ 530 "SELECT * FROM " + self.nom_taula_vers + \ 531 " WHERE {query_clau} AND " + \ 532 self.nom_taula_vers + "." + self.nom_datini + \ 533 " <= A_DAT_VER ORDER BY " + self.nom_datini + " DESC" 534 535 return def_curs_templ.format(params_clau=self.clau_as_def_params_func(def_param=True), 536 query_clau=self.sql_query_eq_clau()) 537 538 @property 539 def def_cursor_actual_reg_vers(self): 540 def_curs_templ = "({params_clau}) IS " \ 541 "SELECT * FROM " + self.nom_taula_vers + \ 542 " WHERE {query_clau} AND " + \ 543 self.nom_taula_vers + "." + self.nom_datfi + \ 544 " IS NULL ORDER BY " + self.nom_datini + " DESC" 545 546 return def_curs_templ.format(params_clau=self.clau_as_def_params_func(def_param=True), 547 query_clau=self.sql_query_eq_clau()) 548 549 @property 550 def params_cursor_clau_reg_new(self): 551 return self.clau_as_def_params_func(prefix_param_templ=":NEW.") 552 553 @property 554 def params_cursor_clau_reg_old(self): 555 return self.clau_as_def_params_func(prefix_param_templ=":OLD.") 556 557 @property 558 def sql_query_eq_clau_for_regaux(self): 559 return self.sql_query_eq_clau(prefix_param=(self.nom_regaux_vers + ".")) 560 561 @property 562 def sql_query_eq_clau_for_new_reg(self): 563 return self.sql_query_eq_clau(prefix_param=":NEW.") 564 565 @property 566 def sql_query_eq_clau_for_prev_reg(self): 567 return self.sql_query_eq_clau(prefix_param=(self.nom_prev_reg_vers + ".")) 568 569 @property 570 def sql_query_eq_clau_for_old_reg(self): 571 return self.sql_query_eq_clau(prefix_param=(self.nom_old_reg_vers + ".")) 572 573 @property 574 def set_camps_new_reg_ver(self): 575 return self.def_set_camps_taula(oper_set=":=", 576 sep_set_camp=";\n", 577 prefix_camp=self.nom_new_reg_vers, 578 prefix_val=":NEW", 579 set_camps_clau=True) 580 581 @property 582 def set_camps_update_reg_ver(self): 583 return self.def_set_camps_taula(prefix_val=":NEW") 584 585 @property 586 def nom_trigger_del_tab_base(self): 587 return self.get_nom_obj_sql(self.nom_taula, 588 self.prefix_delete_trigger, 589 self.sufix_trigger) 590 591 @property 592 def nom_trigger_ins_upd_tab_base(self): 593 return self.get_nom_obj_sql(self.nom_taula, 594 self.prefix_ins_upd_trigger, 595 self.sufix_trigger) 596 597 @property 598 def nom_trigger_del_tab_vers(self): 599 return self.get_nom_obj_sql(self.nom_taula, 600 self.prefix_delete_trigger, 601 self.sufix_taula_vers + self.sufix_trigger) 602 603 @property 604 def nom_trigger_ins_tab_vers(self): 605 return self.get_nom_obj_sql(self.nom_taula, 606 self.prefix_insert_trigger, 607 self.sufix_taula_vers + self.sufix_trigger) 608 609 @property 610 def nom_trigger_upd_tab_vers(self): 611 return self.get_nom_obj_sql(self.nom_taula, 612 self.prefix_update_trigger, 613 self.sufix_taula_vers + self.sufix_trigger) 614 615 @property 616 def nom_constraint_chk_vers(self): 617 return self.get_nom_obj_sql(self.nom_taula, 618 sufix=self.sufix_check_constraint) 619 620 @property 621 def nom_var_prev_reg_vers(self): 622 return self.nom_prev_reg_vers 623 624 @property 625 def nom_var_new_reg_vers(self): 626 return self.nom_new_reg_vers 627 628 @property 629 def nom_var_regaux_vers(self): 630 return self.nom_regaux_vers 631 632 @property 633 def set_camps_clau_regaux_from_new(self): 634 return self.def_set_camps_clau(oper_set=":=", 635 sep_set_camp=";", 636 prefix_camp=self.nom_regaux_vers, 637 prefix_val=":NEW") 638 639 @property 640 def set_camps_clau_regaux_from_old(self): 641 return self.def_set_camps_clau(oper_set=":=", 642 sep_set_camp=";", 643 prefix_camp=self.nom_regaux_vers, 644 prefix_val=":OLD") 645 646 @property 647 def nom_var_old_reg_vers(self): 648 return self.nom_old_reg_vers 649 650 @property 651 def set_camps_oldreg_from_old(self): 652 return self.def_set_camps_taula(oper_set=":=", 653 sep_set_camp=";\n", 654 prefix_camp=self.nom_old_reg_vers, 655 prefix_val=":OLD", 656 set_camps_clau=True) 657 658 @property 659 def nom_datini(self): 660 return "DAT_INI_VER" 661 662 @property 663 def nom_datfi(self): 664 return "DAT_FI_VER" 665 666 @property 667 def nom_idx_datini(self): 668 return self.get_nom_obj_sql(self.nom_taula, self.prefix_index, self.sufix_taula_vers + "_" + self.nom_datini) 669 670 @property 671 def nom_idx_datfi(self): 672 return self.get_nom_obj_sql(self.nom_taula, self.prefix_index, self.sufix_taula_vers + "_" + self.nom_datfi) 673 674 @property 675 def nom_taula_vers_pk(self): 676 return self.get_nom_obj_sql(self.nom_taula, self.prefix_primary_key, self.sufix_taula_vers) 677 678 @property 679 def idx_nom_taula_vers_pk_datini(self): 680 return self.get_nom_obj_sql(self.nom_taula, "PKDAT_", self.sufix_taula_vers + self.sufix_unic) 681 682 @property 683 def nom_taula_vers_datini_chk(self): 684 return self.get_nom_obj_sql(self.nom_taula, "DAT_", self.sufix_taula_vers + self.sufix_check_constraint) 685 686 def get_triggers_extra_tab_base(self): 687 """ 688 (SUBCLASEAR) Retorna lista strings con las definiciones de triggers extra a incluir 689 """ 690 return [] 691 692 @property 693 def triggers_extra_tab_base(self): 694 str_ret = "" 695 l_trgs = self.get_triggers_extra_tab_base() 696 if l_trgs: 697 str_ret = "{a_sql}".format(a_sql="\n".join(l_trgs)) 698 699 return str_ret 700 701 def get_follows_trigger_ins_upd_tab_base(self): 702 """ 703 (SUBCLASEAR) Retorna lista de strings con los nombres de triggers que precederán al trigger de la tabla 704 base que se encargará del versionado (el del nombre dado por property 'self.nom_nom_trigger_ins_upd_tab_base') 705 """ 706 return [] 707 708 @property 709 def follows_trigger_ins_upd_tab_base(self): 710 str_ret = "" 711 l_trgs = self.get_follows_trigger_ins_upd_tab_base() 712 if l_trgs: 713 str_ret = "\nFOLLOWS {a_sql}".format(a_sql=", ".join(l_trgs)) 714 715 return str_ret
175 def convert_ddl_file_to_scd(self, ddl_file, 176 list_extra_camps=None, 177 a_sql_date_version=None, 178 tipo_scd='SCD4'): 179 """ 180 Genera a partir de DDL de tabla de Oracle (ddl_file) un nuevo script sobre el subdirectorio 'ddl_[tipo_scd]' con el mismo nombre pero el sufijo '_VE', con los objectos SQL (create, triggers, index, ...) que permitirán la gestión de versiones de cada cambio sobre dicha tabla. 181 182 Args: 183 ddl_file: path del fichero DDL de la tabla Oracle que se quiere versionar 184 list_extra_camps: (OPC) lista con definicion de campos sql 185 a_sql_date_version: (OPC) senetencia sql que devolverá la fecha de versión. Por defecto 'CURRENT_DATE' 186 tipo_scd: (OPC) el tipo de SCD que se quiere crear. Opciones disponibles: SCD4 y SCD4C. Por defecto 'SCD4' (Slowly changing dimension 4). El tipo SCD4C añade a la tabla de versiones compound triggers de Oracle para mantener integridad fechas sin incurrir en error 'mutating tables' 187 188 Returns: 189 path_file_scd (string) 190 """ 191 192 ok = self._set_parser_ddl_file(ddl_file, list_extra_camps) 193 if ok: 194 self.sql_date_version = self.default_date_version 195 if a_sql_date_version: 196 self.sql_date_version = a_sql_date_version 197 198 return self.create_ddl_scd_file(ddl_file, tipo_scd)
Genera a partir de DDL de tabla de Oracle (ddl_file) un nuevo script sobre el subdirectorio 'ddl_[tipo_scd]' con el mismo nombre pero el sufijo '_VE', con los objectos SQL (create, triggers, index, ...) que permitirán la gestión de versiones de cada cambio sobre dicha tabla.
Arguments:
- ddl_file: path del fichero DDL de la tabla Oracle que se quiere versionar
- list_extra_camps: (OPC) lista con definicion de campos sql
- a_sql_date_version: (OPC) senetencia sql que devolverá la fecha de versión. Por defecto 'CURRENT_DATE'
- tipo_scd: (OPC) el tipo de SCD que se quiere crear. Opciones disponibles: SCD4 y SCD4C. Por defecto 'SCD4' (Slowly changing dimension 4). El tipo SCD4C añade a la tabla de versiones compound triggers de Oracle para mantener integridad fechas sin incurrir en error 'mutating tables'
Returns:
path_file_scd (string)
249 def create_ddl_scd_file(self, ddl_file, tipo_scd): 250 """ 251 Crear fichero DDL SCD según tipo 252 253 Args: 254 ddl_file: 255 tipo_scd: 256 257 Returns: 258 ddl_file_scd (string): path file SCD 259 """ 260 parts_file = os.path.splitext(os.path.basename(ddl_file)) 261 dir_ver = os.path.join(os.path.dirname(ddl_file), "ddls_" + tipo_scd) 262 ddl_file_ver = os.path.join(dir_ver, parts_file[0] + "_VE" + parts_file[1]) 263 264 if not os.path.exists(dir_ver): 265 os.makedirs(dir_ver) 266 267 mode_io = "w" 268 if not os.path.exists(ddl_file_ver): 269 mode_io = "x" 270 271 with open(ddl_file_ver, mode_io, encoding='utf-8') as a_file: 272 a_file.write(self.ddl_parser.as_script_sql) 273 a_file.write("\n") 274 a_file.write(self.get_ddl_scd(tipo_scd=tipo_scd)) 275 276 # Se añade los sql_statements al DDL de la tabla versionada que no hagan referencia a 'ALTER TABLE tab ADD' 277 rex_alter_add = re.compile(r"ALTER TABLE [']?" + self.nom_taula + "[']? ADD", re.IGNORECASE) 278 rex_nom_tab_ve = re.compile(r"[\w'\"]*" + self.nom_taula_vers + r"[\w'\"]*", re.IGNORECASE) 279 for a_stmnt in self.ddl_parser.others_apb_statements(): 280 if rex_alter_add.match(a_stmnt.format_val): 281 continue 282 283 a_elem_pos_stmnt = a_stmnt.get_elem_match_val(rex_ini_pal + self.nom_taula + rex_fin_pal) 284 if a_elem_pos_stmnt: 285 a_elem_pos_stmnt.elem.substitute_val(self.nom_taula, self.nom_taula_vers) 286 287 id_stmnt = a_stmnt.id_principal 288 if id_stmnt is not None and not rex_nom_tab_ve.match(id_stmnt): 289 a_stmnt.substitute_val(id_stmnt, 290 self.get_nom_obj_sql(id_stmnt, sufix=self.sufix_taula_vers)) 291 292 a_file.write("\n\n") 293 a_file.write(a_stmnt.format_val) 294 a_file.write("\n") 295 a_file.write("/") 296 a_file.write("\n") 297 298 return ddl_file_ver
Crear fichero DDL SCD según tipo
Arguments:
- ddl_file:
- tipo_scd:
Returns:
ddl_file_scd (string): path file SCD
300 def get_ddl_tmpl(self, tipo_scd=SCD4): 301 """ 302 Retorna template a usar según tipo_scd 303 304 Args: 305 tipo_scd: 306 307 Returns: 308 template_scd (string): path del template a utilizar 309 """ 310 a_template_scd = "" 311 tipo_scd = tipo_scd.upper() 312 313 if tipo_scd == self.SCD4c: 314 with open(os.path.join(os.path.dirname(__file__), self.templates_tipo[self.SCD4]), 315 encoding='utf8') as a_file: 316 a_template_scd += a_file.read() 317 a_template_scd += "\n" 318 319 with open(os.path.join(os.path.dirname(__file__), self.templates_tipo[tipo_scd]), 320 encoding='utf8') as a_file: 321 a_template_scd += a_file.read() 322 323 return a_template_scd
Retorna template a usar según tipo_scd
Arguments:
- tipo_scd:
Returns:
template_scd (string): path del template a utilizar
325 def get_ddl_scd(self, tipo_scd=SCD4): 326 """ 327 Retorna ddl SCD base 328 Args: 329 tipo_scd: 330 331 Returns: 332 a_ddl_scd (string): path ddl scd base 333 """ 334 a_ddl_scd = self.get_ddl_tmpl(tipo_scd).format(self=self) 335 336 return a_ddl_scd
Retorna ddl SCD base
Arguments:
- tipo_scd:
Returns:
a_ddl_scd (string): path ddl scd base
338 def add_property_func(self, nom_prop, a_func): 339 """ 340 Añade funcion a self.__class__ para poder usar templates personalizados 341 342 Args: 343 nom_prop: 344 a_func: 345 """ 346 setattr(self.__class__, nom_prop, property(a_func))
Añade funcion a self.__class__ para poder usar templates personalizados
Arguments:
- nom_prop:
- a_func:
348 @staticmethod 349 def format_nom_ddl(nom_ddl): 350 """ 351 Para cualquier string que reciba como nombre de elemento en DDL lo formatea a mayúsculas y sin " 352 Args: 353 nom_ddl: 354 355 Returns: 356 string 357 """ 358 # 359 return nom_ddl.strip().replace('"', '').upper()
Para cualquier string que reciba como nombre de elemento en DDL lo formatea a mayúsculas y sin "
Arguments:
- nom_ddl:
Returns:
string
361 def clau_as_def_params_func(self, prefix_param_templ=prefix_val_param, def_param=False): 362 """ 363 Retorna clave tabla como parametros de funcion de PL/SQL 364 Args: 365 prefix_param_templ: 366 def_param: 367 368 Returns: 369 string 370 """ 371 sufix_param_templ = "" 372 if def_param: 373 sufix_param_templ = " " + self.nom_taula_vers + ".{nom_camp_clau}%TYPE" 374 375 param_templ = prefix_param_templ + "{nom_camp_clau}" + sufix_param_templ 376 claus_as_params = [] 377 for nom_camp in self.nom_camps_clau: 378 claus_as_params.append(param_templ.format(nom_camp_clau=nom_camp)) 379 380 return ", ".join(claus_as_params)
Retorna clave tabla como parametros de funcion de PL/SQL
Arguments:
- prefix_param_templ:
- def_param:
Returns:
string
382 def sql_query_eq_clau(self, prefix_param=prefix_val_param): 383 """ 384 Retorna clave tabla como query SQL 385 Args: 386 prefix_param: 387 388 Returns: 389 string 390 """ 391 prefix_param_templ = "" 392 if prefix_param is not None: 393 prefix_param_templ = prefix_param 394 395 query_eq_clau_templ = self.nom_taula_vers + ".{nom_clau} = " + prefix_param_templ + "{nom_clau}" 396 list_query_claus = [] 397 for nom_camp in self.nom_camps_clau: 398 list_query_claus.append(query_eq_clau_templ.format(nom_clau=nom_camp)) 399 400 return " AND ".join(list_query_claus)
Retorna clave tabla como query SQL
Arguments:
- prefix_param:
Returns:
string
402 def def_set_camps_clau(self, 403 oper_set="=", 404 sep_set_camp=", ", 405 prefix_camp="", 406 prefix_val=""): 407 """ 408 Retorna SQL para hacer SET de valores de los campos clave 409 Args: 410 oper_set: 411 sep_set_camp: 412 prefix_camp: 413 prefix_val: 414 415 Returns: 416 string 417 """ 418 if len(prefix_camp) != 0: 419 prefix_camp += "." 420 if len(prefix_val) != 0: 421 prefix_val += "." 422 423 set_camp_templ = prefix_camp + "{nom_camp} " + oper_set + " " + prefix_val + "{nom_camp}" 424 425 list_set_camps = [] 426 for a_nom_camp in self.nom_camps_clau: 427 list_set_camps.append(set_camp_templ.format(nom_camp=a_nom_camp)) 428 429 return sep_set_camp.join(list_set_camps)
Retorna SQL para hacer SET de valores de los campos clave
Arguments:
- oper_set:
- sep_set_camp:
- prefix_camp:
- prefix_val:
Returns:
string
431 def def_set_camps_taula(self, 432 oper_set="=", 433 sep_set_camp=", ", 434 prefix_camp="", 435 prefix_val="", 436 set_camps_clau=False): 437 """ 438 Retorna SQL para hacer SET de los valores de los campos 439 Args: 440 oper_set: 441 sep_set_camp: 442 prefix_camp: 443 prefix_val: 444 set_camps_clau: 445 446 Returns: 447 string 448 """ 449 if len(prefix_camp) != 0: 450 prefix_camp += "." 451 if len(prefix_val) != 0: 452 prefix_val += "." 453 454 set_camp_templ = prefix_camp + "{nom_camp} " + oper_set + " " + prefix_val + "{nom_camp}" 455 456 list_set_camps = [] 457 for a_nom_camp in self.def_camps_taula.keys(): 458 if not set_camps_clau and a_nom_camp in self.nom_camps_clau: 459 continue 460 461 list_set_camps.append(set_camp_templ.format(nom_camp=a_nom_camp)) 462 463 return sep_set_camp.join(list_set_camps)
Retorna SQL para hacer SET de los valores de los campos
Arguments:
- oper_set:
- sep_set_camp:
- prefix_camp:
- prefix_val:
- set_camps_clau:
Returns:
string
465 def get_nom_obj_sql(self, nom_base, prefix="", sufix=""): 466 """ 467 Devuelve nombre objecto SQL con prefijo y sufijo ajustado a la longitud máxima de nombres en PL/SQL 468 Args: 469 nom_base: 470 prefix: 471 sufix: 472 473 Returns: 474 string 475 """ 476 return SqlParser.get_nom_obj_sql(nom_base, prefix, sufix)
Devuelve nombre objecto SQL con prefijo y sufijo ajustado a la longitud máxima de nombres en PL/SQL
Arguments:
- nom_base:
- prefix:
- sufix:
Returns:
string
527 @property 528 def def_cursor_prev_date_reg_vers(self): 529 def_curs_templ = "({params_clau}, A_DAT_VER DATE) IS " \ 530 "SELECT * FROM " + self.nom_taula_vers + \ 531 " WHERE {query_clau} AND " + \ 532 self.nom_taula_vers + "." + self.nom_datini + \ 533 " <= A_DAT_VER ORDER BY " + self.nom_datini + " DESC" 534 535 return def_curs_templ.format(params_clau=self.clau_as_def_params_func(def_param=True), 536 query_clau=self.sql_query_eq_clau())
538 @property 539 def def_cursor_actual_reg_vers(self): 540 def_curs_templ = "({params_clau}) IS " \ 541 "SELECT * FROM " + self.nom_taula_vers + \ 542 " WHERE {query_clau} AND " + \ 543 self.nom_taula_vers + "." + self.nom_datfi + \ 544 " IS NULL ORDER BY " + self.nom_datini + " DESC" 545 546 return def_curs_templ.format(params_clau=self.clau_as_def_params_func(def_param=True), 547 query_clau=self.sql_query_eq_clau())
686 def get_triggers_extra_tab_base(self): 687 """ 688 (SUBCLASEAR) Retorna lista strings con las definiciones de triggers extra a incluir 689 """ 690 return []
(SUBCLASEAR) Retorna lista strings con las definiciones de triggers extra a incluir
701 def get_follows_trigger_ins_upd_tab_base(self): 702 """ 703 (SUBCLASEAR) Retorna lista de strings con los nombres de triggers que precederán al trigger de la tabla 704 base que se encargará del versionado (el del nombre dado por property 'self.nom_nom_trigger_ins_upd_tab_base') 705 """ 706 return []
(SUBCLASEAR) Retorna lista de strings con los nombres de triggers que precederán al trigger de la tabla base que se encargará del versionado (el del nombre dado por property 'self.nom_nom_trigger_ins_upd_tab_base')