mssql.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. from sqlalchemy.ext.compiler import compiles
  2. from .. import util
  3. from .impl import DefaultImpl
  4. from .base import alter_table, AddColumn, ColumnName, RenameTable,\
  5. format_table_name, format_column_name, ColumnNullable, alter_column,\
  6. format_server_default, ColumnDefault, format_type, ColumnType
  7. from sqlalchemy.sql.expression import ClauseElement, Executable
  8. class MSSQLImpl(DefaultImpl):
  9. __dialect__ = 'mssql'
  10. transactional_ddl = True
  11. batch_separator = "GO"
  12. def __init__(self, *arg, **kw):
  13. super(MSSQLImpl, self).__init__(*arg, **kw)
  14. self.batch_separator = self.context_opts.get(
  15. "mssql_batch_separator",
  16. self.batch_separator)
  17. def _exec(self, construct, *args, **kw):
  18. result = super(MSSQLImpl, self)._exec(construct, *args, **kw)
  19. if self.as_sql and self.batch_separator:
  20. self.static_output(self.batch_separator)
  21. return result
  22. def emit_begin(self):
  23. self.static_output("BEGIN TRANSACTION" + self.command_terminator)
  24. def emit_commit(self):
  25. super(MSSQLImpl, self).emit_commit()
  26. if self.as_sql and self.batch_separator:
  27. self.static_output(self.batch_separator)
  28. def alter_column(self, table_name, column_name,
  29. nullable=None,
  30. server_default=False,
  31. name=None,
  32. type_=None,
  33. schema=None,
  34. existing_type=None,
  35. existing_server_default=None,
  36. existing_nullable=None,
  37. **kw
  38. ):
  39. if nullable is not None and existing_type is None:
  40. if type_ is not None:
  41. existing_type = type_
  42. # the NULL/NOT NULL alter will handle
  43. # the type alteration
  44. type_ = None
  45. else:
  46. raise util.CommandError(
  47. "MS-SQL ALTER COLUMN operations "
  48. "with NULL or NOT NULL require the "
  49. "existing_type or a new type_ be passed.")
  50. super(MSSQLImpl, self).alter_column(
  51. table_name, column_name,
  52. nullable=nullable,
  53. type_=type_,
  54. schema=schema,
  55. existing_type=existing_type,
  56. existing_nullable=existing_nullable,
  57. **kw
  58. )
  59. if server_default is not False:
  60. if existing_server_default is not False or \
  61. server_default is None:
  62. self._exec(
  63. _ExecDropConstraint(
  64. table_name, column_name,
  65. 'sys.default_constraints')
  66. )
  67. if server_default is not None:
  68. super(MSSQLImpl, self).alter_column(
  69. table_name, column_name,
  70. schema=schema,
  71. server_default=server_default)
  72. if name is not None:
  73. super(MSSQLImpl, self).alter_column(
  74. table_name, column_name,
  75. schema=schema,
  76. name=name)
  77. def bulk_insert(self, table, rows, **kw):
  78. if self.as_sql:
  79. self._exec(
  80. "SET IDENTITY_INSERT %s ON" %
  81. self.dialect.identifier_preparer.format_table(table)
  82. )
  83. super(MSSQLImpl, self).bulk_insert(table, rows, **kw)
  84. self._exec(
  85. "SET IDENTITY_INSERT %s OFF" %
  86. self.dialect.identifier_preparer.format_table(table)
  87. )
  88. else:
  89. super(MSSQLImpl, self).bulk_insert(table, rows, **kw)
  90. def drop_column(self, table_name, column, **kw):
  91. drop_default = kw.pop('mssql_drop_default', False)
  92. if drop_default:
  93. self._exec(
  94. _ExecDropConstraint(
  95. table_name, column,
  96. 'sys.default_constraints')
  97. )
  98. drop_check = kw.pop('mssql_drop_check', False)
  99. if drop_check:
  100. self._exec(
  101. _ExecDropConstraint(
  102. table_name, column,
  103. 'sys.check_constraints')
  104. )
  105. drop_fks = kw.pop('mssql_drop_foreign_key', False)
  106. if drop_fks:
  107. self._exec(
  108. _ExecDropFKConstraint(table_name, column)
  109. )
  110. super(MSSQLImpl, self).drop_column(table_name, column, **kw)
  111. class _ExecDropConstraint(Executable, ClauseElement):
  112. def __init__(self, tname, colname, type_):
  113. self.tname = tname
  114. self.colname = colname
  115. self.type_ = type_
  116. class _ExecDropFKConstraint(Executable, ClauseElement):
  117. def __init__(self, tname, colname):
  118. self.tname = tname
  119. self.colname = colname
  120. @compiles(_ExecDropConstraint, 'mssql')
  121. def _exec_drop_col_constraint(element, compiler, **kw):
  122. tname, colname, type_ = element.tname, element.colname, element.type_
  123. # from http://www.mssqltips.com/sqlservertip/1425/\
  124. # working-with-default-constraints-in-sql-server/
  125. # TODO: needs table formatting, etc.
  126. return """declare @const_name varchar(256)
  127. select @const_name = [name] from %(type)s
  128. where parent_object_id = object_id('%(tname)s')
  129. and col_name(parent_object_id, parent_column_id) = '%(colname)s'
  130. exec('alter table %(tname_quoted)s drop constraint ' + @const_name)""" % {
  131. 'type': type_,
  132. 'tname': tname,
  133. 'colname': colname,
  134. 'tname_quoted': format_table_name(compiler, tname, None),
  135. }
  136. @compiles(_ExecDropFKConstraint, 'mssql')
  137. def _exec_drop_col_fk_constraint(element, compiler, **kw):
  138. tname, colname = element.tname, element.colname
  139. return """declare @const_name varchar(256)
  140. select @const_name = [name] from
  141. sys.foreign_keys fk join sys.foreign_key_columns fkc
  142. on fk.object_id=fkc.constraint_object_id
  143. where fkc.parent_object_id = object_id('%(tname)s')
  144. and col_name(fkc.parent_object_id, fkc.parent_column_id) = '%(colname)s'
  145. exec('alter table %(tname_quoted)s drop constraint ' + @const_name)""" % {
  146. 'tname': tname,
  147. 'colname': colname,
  148. 'tname_quoted': format_table_name(compiler, tname, None),
  149. }
  150. @compiles(AddColumn, 'mssql')
  151. def visit_add_column(element, compiler, **kw):
  152. return "%s %s" % (
  153. alter_table(compiler, element.table_name, element.schema),
  154. mssql_add_column(compiler, element.column, **kw)
  155. )
  156. def mssql_add_column(compiler, column, **kw):
  157. return "ADD %s" % compiler.get_column_specification(column, **kw)
  158. @compiles(ColumnNullable, 'mssql')
  159. def visit_column_nullable(element, compiler, **kw):
  160. return "%s %s %s %s" % (
  161. alter_table(compiler, element.table_name, element.schema),
  162. alter_column(compiler, element.column_name),
  163. format_type(compiler, element.existing_type),
  164. "NULL" if element.nullable else "NOT NULL"
  165. )
  166. @compiles(ColumnDefault, 'mssql')
  167. def visit_column_default(element, compiler, **kw):
  168. # TODO: there can also be a named constraint
  169. # with ADD CONSTRAINT here
  170. return "%s ADD DEFAULT %s FOR %s" % (
  171. alter_table(compiler, element.table_name, element.schema),
  172. format_server_default(compiler, element.default),
  173. format_column_name(compiler, element.column_name)
  174. )
  175. @compiles(ColumnName, 'mssql')
  176. def visit_rename_column(element, compiler, **kw):
  177. return "EXEC sp_rename '%s.%s', %s, 'COLUMN'" % (
  178. format_table_name(compiler, element.table_name, element.schema),
  179. format_column_name(compiler, element.column_name),
  180. format_column_name(compiler, element.newname)
  181. )
  182. @compiles(ColumnType, 'mssql')
  183. def visit_column_type(element, compiler, **kw):
  184. return "%s %s %s" % (
  185. alter_table(compiler, element.table_name, element.schema),
  186. alter_column(compiler, element.column_name),
  187. format_type(compiler, element.type_)
  188. )
  189. @compiles(RenameTable, 'mssql')
  190. def visit_rename_table(element, compiler, **kw):
  191. return "EXEC sp_rename '%s', %s" % (
  192. format_table_name(compiler, element.table_name, element.schema),
  193. format_table_name(compiler, element.new_table_name, None)
  194. )