Guest User

Untitled

a guest
Oct 20th, 2017
420
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.79 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # coding=utf-8
  3. # pylint: disable=C0103,E1133,W0621
  4. """This script adjust the collation of SQL Server database"""
  5.  
  6. __author__ = "Fábio Matavelli <fabio.matavelli@amaro.com>"
  7.  
  8. import argparse
  9. import os
  10. import logging
  11. from sqlalchemy import create_engine, MetaData, Table
  12. from sqlalchemy.engine import reflection
  13. from sqlalchemy.exc import InterfaceError, OperationalError, ProgrammingError, NoSuchTableError
  14. from sqlalchemy.sql.sqltypes import VARCHAR, NVARCHAR, CHAR, NCHAR
  15.  
  16. def alter_column(table, column, tp, length, collate, null):
  17. """Function to generate the ALTER query"""
  18.  
  19. query = "ALTER TABLE [{table}] " \
  20. "ALTER COLUMN [{column}] " \
  21. "{type}({length}) " \
  22. "COLLATE {collate} " \
  23. "{null};".format(table=table, column=column,
  24. null="NULL" if null else "NOT NULL",
  25. type=tp, length=length,
  26. collate=collate)
  27. return query
  28.  
  29. current_dir = os.path.dirname(os.path.realpath(__file__))
  30.  
  31. log = logging.Logger(name="change_collate")
  32.  
  33. os.environ["TDSDUMP"] = "{0}\\freetds.log".format(current_dir)
  34. os.environ["FREETDSCONF"] = "{0}\\freetds.conf".format(current_dir)
  35.  
  36. parser = argparse.ArgumentParser(description="Change collate of SQL Server database")
  37. parser.add_argument("--host", help="Database hostname", required=True, metavar="<database>")
  38. parser.add_argument("--username", help="Database username", metavar="<username>")
  39. parser.add_argument("--password", help="Database password", metavar="<password>")
  40. parser.add_argument("--db", help="Database name", metavar="<database>", required=True)
  41. parser.add_argument("--old-collation", help="Old collation (to change from)", required=True,
  42. metavar="<collation>")
  43. parser.add_argument("--new-collation", help="New collation (to change for)", required=True,
  44. metavar="<collation>")
  45. parser.add_argument("--tables", help="Tables name", metavar="<table>",
  46. nargs="+")
  47. parser.add_argument("--views", help="Views name", metavar="<view>",
  48. nargs="+")
  49. parser.add_argument("--log-level", help="Log level",
  50. choices=["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"],
  51. default="INFO")
  52.  
  53. if __name__ == "__main__":
  54. args = parser.parse_args()
  55. log.setLevel(args.log_level)
  56. auth = None
  57.  
  58. if args.username is not None:
  59. auth = "{username}{password}@".format(username=args.username,
  60. password=":" + args.password
  61. if args.password is not None else "")
  62.  
  63. engine = create_engine(r"mssql+pymssql://{auth}{hostname}/{database}?charset=UTF-8"
  64. .format(hostname=args.host, auth=auth or "", database=args.db),
  65. echo=True, deprecate_large_types=True)
  66.  
  67. try:
  68. insp = reflection.Inspector.from_engine(engine)
  69. except (InterfaceError, OperationalError) as err:
  70. log.error("Error trying to connect to database %s with DSN %s", err, engine.url)
  71. exit()
  72.  
  73. metadata = MetaData()
  74.  
  75. if not args.views:
  76. views = insp.get_view_names()
  77. else:
  78. views = args.views
  79.  
  80. if not args.tables:
  81. tables = [table for table in insp.get_table_names() if table not in views]
  82. else:
  83. tables = [table for table in args.tables if table not in views]
  84.  
  85. if not args.tables and args.views:
  86. tables = []
  87. elif args.tables and not args.views:
  88. views = []
  89.  
  90. for table in tables:
  91. __map__ = {}
  92. __indexes__ = {}
  93. __indexes_trash__ = []
  94.  
  95. log.info("Getting info of table %s", table)
  96. try:
  97. table_class = Table(table, metadata, autoload=True, autoload_with=engine)
  98. except NoSuchTableError as err:
  99. log.error("Table %s not found", table)
  100. continue
  101.  
  102. log.info("Registering indexes for table %s", table)
  103. for index in table_class.indexes:
  104. if index.name is None:
  105. continue
  106.  
  107. __indexes__.setdefault(index.name, index)
  108.  
  109. for column in index.columns:
  110. if not __map__.get(column.name):
  111. __map__.setdefault(column.name, [])
  112.  
  113. __map__[column.name].append(index.name)
  114.  
  115. log.info("Changing columns in table %s", table)
  116. for column in [col for col in table_class.columns
  117. if isinstance(col.type, (type(VARCHAR()), type(NVARCHAR()),
  118. type(CHAR()), type(NCHAR()))) and
  119. col.type.collation != args.new_collation]:
  120.  
  121. for index in [idx for idx in __map__.get(column.name) or []
  122. if idx not in __indexes_trash__]:
  123. log.info("Droping index %s", index)
  124. __indexes__.get(index).drop(engine)
  125. __indexes_trash__.append(index)
  126.  
  127. column.type.collation = args.new_collation
  128.  
  129. query = alter_column(table=table, column=column.name,
  130. tp=column.type.__visit_name__, null=column.nullable,
  131. length=column.type.length or "max",
  132. collate=column.type.collation)
  133.  
  134. log.info("Altering column %s", column)
  135.  
  136. try:
  137. engine.execute(query)
  138. except (ProgrammingError, OperationalError) as err:
  139. log.warning("Error trying to change column %s collate. %s", column, err)
  140.  
  141. if __indexes_trash__ != []:
  142. log.info("Recreating dropped indexes of table %s", table)
  143.  
  144. for index in __indexes_trash__:
  145. log.info("Creating index %s", index)
  146. __indexes__.get(index).create(engine)
  147.  
  148. del(__map__, __indexes__, __indexes_trash__)
Add Comment
Please, Sign In to add comment