##// END OF EJS Templates
engine: use new visitor-callable from latest version
super-admin -
r5164:19fefcb8 default
parent child Browse files
Show More
@@ -1,701 +1,715 b''
1 1 """
2 2 Schema module providing common schema operations.
3 3 """
4 4 import abc
5 5 try: # Python 3
6 6 from collections.abc import MutableMapping as DictMixin
7 7 except ImportError: # Python 2
8 8 from UserDict import DictMixin
9 9 import warnings
10 10
11 11 import sqlalchemy
12 12
13 13 from sqlalchemy.schema import ForeignKeyConstraint
14 14 from sqlalchemy.schema import UniqueConstraint
15 15
16 16 from rhodecode.lib.dbmigrate.migrate.exceptions import *
17 17 from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_07, SQLA_08
18 18 from rhodecode.lib.dbmigrate.migrate.changeset import util
19 19 from rhodecode.lib.dbmigrate.migrate.changeset.databases.visitor import (
20 20 get_engine_visitor, run_single_visitor)
21 21
22 22
23 23 __all__ = [
24 24 'create_column',
25 25 'drop_column',
26 26 'alter_column',
27 27 'rename_table',
28 28 'rename_index',
29 29 'ChangesetTable',
30 30 'ChangesetColumn',
31 31 'ChangesetIndex',
32 32 'ChangesetDefaultClause',
33 33 'ColumnDelta',
34 34 ]
35 35
36 36
37 37 def create_column(column, table=None, *p, **kw):
38 38 """Create a column, given the table.
39 39
40 40 API to :meth:`ChangesetColumn.create`.
41 41 """
42 42 if table is not None:
43 43 return table.create_column(column, *p, **kw)
44 44 return column.create(*p, **kw)
45 45
46 46
47 47 def drop_column(column, table=None, *p, **kw):
48 48 """Drop a column, given the table.
49 49
50 50 API to :meth:`ChangesetColumn.drop`.
51 51 """
52 52 if table is not None:
53 53 return table.drop_column(column, *p, **kw)
54 54 return column.drop(*p, **kw)
55 55
56 56
57 57 def rename_table(table, name, engine=None, **kw):
58 58 """Rename a table.
59 59
60 60 If Table instance is given, engine is not used.
61 61
62 62 API to :meth:`ChangesetTable.rename`.
63 63
64 64 :param table: Table to be renamed.
65 65 :param name: New name for Table.
66 66 :param engine: Engine instance.
67 67 :type table: string or Table instance
68 68 :type name: string
69 69 :type engine: obj
70 70 """
71 71 table = _to_table(table, engine)
72 72 table.rename(name, **kw)
73 73
74 74
75 75 def rename_index(index, name, table=None, engine=None, **kw):
76 76 """Rename an index.
77 77
78 78 If Index instance is given,
79 79 table and engine are not used.
80 80
81 81 API to :meth:`ChangesetIndex.rename`.
82 82
83 83 :param index: Index to be renamed.
84 84 :param name: New name for index.
85 85 :param table: Table to which Index is reffered.
86 86 :param engine: Engine instance.
87 87 :type index: string or Index instance
88 88 :type name: string
89 89 :type table: string or Table instance
90 90 :type engine: obj
91 91 """
92 92 index = _to_index(index, table, engine)
93 93 index.rename(name, **kw)
94 94
95 95
96 96 def alter_column(*p, **k):
97 97 """Alter a column.
98 98
99 99 This is a helper function that creates a :class:`ColumnDelta` and
100 100 runs it.
101 101
102 102 :argument column:
103 103 The name of the column to be altered or a
104 104 :class:`ChangesetColumn` column representing it.
105 105
106 106 :param table:
107 107 A :class:`~sqlalchemy.schema.Table` or table name to
108 108 for the table where the column will be changed.
109 109
110 110 :param engine:
111 111 The :class:`~sqlalchemy.engine.base.Engine` to use for table
112 112 reflection and schema alterations.
113 113
114 114 :returns: A :class:`ColumnDelta` instance representing the change.
115 115
116 116
117 117 """
118 118
119 119 if 'table' not in k and isinstance(p[0], sqlalchemy.Column):
120 120 k['table'] = p[0].table
121 121 if 'engine' not in k:
122 122 k['engine'] = k['table'].bind
123 123
124 124 # deprecation
125 125 if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
126 126 warnings.warn(
127 127 "Passing a Column object to alter_column is deprecated."
128 128 " Just pass in keyword parameters instead.",
129 129 MigrateDeprecationWarning
130 130 )
131 131 engine = k['engine']
132 132
133 133 # enough tests seem to break when metadata is always altered
134 134 # that this crutch has to be left in until they can be sorted
135 135 # out
136 136 k['alter_metadata']=True
137 137
138 138 delta = ColumnDelta(*p, **k)
139 139
140 140 visitorcallable = get_engine_visitor(engine, 'schemachanger')
141 engine._run_visitor(visitorcallable, delta)
141 _run_visitor(engine, visitorcallable, delta)
142 142
143 143 return delta
144 144
145 145
146 146 def _to_table(table, engine=None):
147 147 """Return if instance of Table, else construct new with metadata"""
148 148 if isinstance(table, sqlalchemy.Table):
149 149 return table
150 150
151 151 # Given: table name, maybe an engine
152 152 meta = sqlalchemy.MetaData()
153 153 if engine is not None:
154 154 meta.bind = engine
155 155 return sqlalchemy.Table(table, meta)
156 156
157 157
158 158 def _to_index(index, table=None, engine=None):
159 159 """Return if instance of Index, else construct new with metadata"""
160 160 if isinstance(index, sqlalchemy.Index):
161 161 return index
162 162
163 163 # Given: index name; table name required
164 164 table = _to_table(table, engine)
165 165 ret = sqlalchemy.Index(index)
166 166 ret.table = table
167 167 return ret
168 168
169 169
170 def _run_visitor(
171 connectable, visitorcallable, element, connection=None, **kwargs
172 ):
173 if connection is not None:
174 visitorcallable(
175 connection.dialect, connection, **kwargs).traverse_single(element)
176 else:
177 conn = connectable.connect()
178 try:
179 visitorcallable(
180 conn.dialect, conn, **kwargs).traverse_single(element)
181 finally:
182 conn.close()
183
170 184
171 185 # Python3: if we just use:
172 186 #
173 187 # class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem):
174 188 # ...
175 189 #
176 190 # We get the following error:
177 191 # TypeError: metaclass conflict: the metaclass of a derived class must be a
178 192 # (non-strict) subclass of the metaclasses of all its bases.
179 193 #
180 194 # The complete inheritance/metaclass relationship list of ColumnDelta can be
181 195 # summarized by this following dot file:
182 196 #
183 197 # digraph test123 {
184 198 # ColumnDelta -> MutableMapping;
185 199 # MutableMapping -> Mapping;
186 200 # Mapping -> {Sized Iterable Container};
187 201 # {Sized Iterable Container} -> ABCMeta[style=dashed];
188 202 #
189 203 # ColumnDelta -> SchemaItem;
190 204 # SchemaItem -> {SchemaEventTarget Visitable};
191 205 # SchemaEventTarget -> object;
192 206 # Visitable -> {VisitableType object} [style=dashed];
193 207 # VisitableType -> type;
194 208 # }
195 209 #
196 210 # We need to use a metaclass that inherits from all the metaclasses of
197 211 # DictMixin and sqlalchemy.schema.SchemaItem. Let's call it "MyMeta".
198 212 class MyMeta(sqlalchemy.sql.visitors.VisitableType, abc.ABCMeta, object):
199 213 pass
200 214
201 215
202 216 class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem, metaclass=MyMeta):
203 217 """Extracts the differences between two columns/column-parameters
204 218
205 219 May receive parameters arranged in several different ways:
206 220
207 221 * **current_column, new_column, \*p, \*\*kw**
208 222 Additional parameters can be specified to override column
209 223 differences.
210 224
211 225 * **current_column, \*p, \*\*kw**
212 226 Additional parameters alter current_column. Table name is extracted
213 227 from current_column object.
214 228 Name is changed to current_column.name from current_name,
215 229 if current_name is specified.
216 230
217 231 * **current_col_name, \*p, \*\*kw**
218 232 Table kw must specified.
219 233
220 234 :param table: Table at which current Column should be bound to.\
221 235 If table name is given, reflection will be used.
222 236 :type table: string or Table instance
223 237
224 238 :param metadata: A :class:`MetaData` instance to store
225 239 reflected table names
226 240
227 241 :param engine: When reflecting tables, either engine or metadata must \
228 242 be specified to acquire engine object.
229 243 :type engine: :class:`Engine` instance
230 244 :returns: :class:`ColumnDelta` instance provides interface for altered attributes to \
231 245 `result_column` through :func:`dict` alike object.
232 246
233 247 * :class:`ColumnDelta`.result_column is altered column with new attributes
234 248
235 249 * :class:`ColumnDelta`.current_name is current name of column in db
236 250
237 251
238 252 """
239 253
240 254 # Column attributes that can be altered
241 255 diff_keys = ('name', 'type', 'primary_key', 'nullable',
242 256 'server_onupdate', 'server_default', 'autoincrement')
243 257 diffs = dict()
244 258 __visit_name__ = 'column'
245 259
246 260 def __init__(self, *p, **kw):
247 261 # 'alter_metadata' is not a public api. It exists purely
248 262 # as a crutch until the tests that fail when 'alter_metadata'
249 263 # behaviour always happens can be sorted out
250 264 self.alter_metadata = kw.pop("alter_metadata", False)
251 265
252 266 self.meta = kw.pop("metadata", None)
253 267 self.engine = kw.pop("engine", None)
254 268
255 269 # Things are initialized differently depending on how many column
256 270 # parameters are given. Figure out how many and call the appropriate
257 271 # method.
258 272 if len(p) >= 1 and isinstance(p[0], sqlalchemy.Column):
259 273 # At least one column specified
260 274 if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
261 275 # Two columns specified
262 276 diffs = self.compare_2_columns(*p, **kw)
263 277 else:
264 278 # Exactly one column specified
265 279 diffs = self.compare_1_column(*p, **kw)
266 280 else:
267 281 # Zero columns specified
268 282 if not len(p) or not isinstance(p[0], str):
269 283 raise ValueError("First argument must be column name")
270 284 diffs = self.compare_parameters(*p, **kw)
271 285
272 286 self.apply_diffs(diffs)
273 287
274 288 def __repr__(self):
275 289 return '<ColumnDelta altermetadata=%r, %s>' % (
276 290 self.alter_metadata,
277 291 super(ColumnDelta, self).__repr__()
278 292 )
279 293
280 294 def __getitem__(self, key):
281 295 if key not in list(self.keys()):
282 296 raise KeyError("No such diff key, available: %s" % self.diffs )
283 297 return getattr(self.result_column, key)
284 298
285 299 def __setitem__(self, key, value):
286 300 if key not in list(self.keys()):
287 301 raise KeyError("No such diff key, available: %s" % self.diffs )
288 302 setattr(self.result_column, key, value)
289 303
290 304 def __delitem__(self, key):
291 305 raise NotImplementedError
292 306
293 307 def __len__(self):
294 308 raise NotImplementedError
295 309
296 310 def __iter__(self):
297 311 raise NotImplementedError
298 312
299 313 def keys(self):
300 314 return list(self.diffs.keys())
301 315
302 316 def compare_parameters(self, current_name, *p, **k):
303 317 """Compares Column objects with reflection"""
304 318 self.table = k.pop('table')
305 319 self.result_column = self._table.c.get(current_name)
306 320 if len(p):
307 321 k = self._extract_parameters(p, k, self.result_column)
308 322 return k
309 323
310 324 def compare_1_column(self, col, *p, **k):
311 325 """Compares one Column object"""
312 326 self.table = k.pop('table', None)
313 327 if self.table is None:
314 328 self.table = col.table
315 329 self.result_column = col
316 330 if len(p):
317 331 k = self._extract_parameters(p, k, self.result_column)
318 332 return k
319 333
320 334 def compare_2_columns(self, old_col, new_col, *p, **k):
321 335 """Compares two Column objects"""
322 336 self.process_column(new_col)
323 337 self.table = k.pop('table', None)
324 338 # we cannot use bool() on table in SA06
325 339 if self.table is None:
326 340 self.table = old_col.table
327 341 if self.table is None:
328 342 new_col.table
329 343 self.result_column = old_col
330 344
331 345 # set differences
332 346 # leave out some stuff for later comp
333 347 for key in (set(self.diff_keys) - set(('type',))):
334 348 val = getattr(new_col, key, None)
335 349 if getattr(self.result_column, key, None) != val:
336 350 k.setdefault(key, val)
337 351
338 352 # inspect types
339 353 if not self.are_column_types_eq(self.result_column.type, new_col.type):
340 354 k.setdefault('type', new_col.type)
341 355
342 356 if len(p):
343 357 k = self._extract_parameters(p, k, self.result_column)
344 358 return k
345 359
346 360 def apply_diffs(self, diffs):
347 361 """Populate dict and column object with new values"""
348 362 self.diffs = diffs
349 363 for key in self.diff_keys:
350 364 if key in diffs:
351 365 setattr(self.result_column, key, diffs[key])
352 366
353 367 self.process_column(self.result_column)
354 368
355 369 # create an instance of class type if not yet
356 370 if 'type' in diffs and callable(self.result_column.type):
357 371 self.result_column.type = self.result_column.type()
358 372
359 373 # add column to the table
360 374 if self.table is not None and self.alter_metadata:
361 375 self.result_column.add_to_table(self.table)
362 376
363 377 def are_column_types_eq(self, old_type, new_type):
364 378 """Compares two types to be equal"""
365 379 ret = old_type.__class__ == new_type.__class__
366 380
367 381 # String length is a special case
368 382 if ret and isinstance(new_type, sqlalchemy.types.String):
369 383 ret = (getattr(old_type, 'length', None) == \
370 384 getattr(new_type, 'length', None))
371 385 return ret
372 386
373 387 def _extract_parameters(self, p, k, column):
374 388 """Extracts data from p and modifies diffs"""
375 389 p = list(p)
376 390 while len(p):
377 391 if isinstance(p[0], str):
378 392 k.setdefault('name', p.pop(0))
379 393 elif isinstance(p[0], sqlalchemy.types.TypeEngine):
380 394 k.setdefault('type', p.pop(0))
381 395 elif callable(p[0]):
382 396 p[0] = p[0]()
383 397 else:
384 398 break
385 399
386 400 if len(p):
387 401 new_col = column.copy_fixed()
388 402 new_col._init_items(*p)
389 403 k = self.compare_2_columns(column, new_col, **k)
390 404 return k
391 405
392 406 def process_column(self, column):
393 407 """Processes default values for column"""
394 408 # XXX: this is a snippet from SA processing of positional parameters
395 409 toinit = list()
396 410
397 411 if column.server_default is not None:
398 412 if isinstance(column.server_default, sqlalchemy.FetchedValue):
399 413 toinit.append(column.server_default)
400 414 else:
401 415 toinit.append(sqlalchemy.DefaultClause(column.server_default))
402 416 if column.server_onupdate is not None:
403 417 if isinstance(column.server_onupdate, FetchedValue):
404 418 toinit.append(column.server_default)
405 419 else:
406 420 toinit.append(sqlalchemy.DefaultClause(column.server_onupdate,
407 421 for_update=True))
408 422 if toinit:
409 423 column._init_items(*toinit)
410 424
411 425 def _get_table(self):
412 426 return getattr(self, '_table', None)
413 427
414 428 def _set_table(self, table):
415 429 if isinstance(table, str):
416 430 if self.alter_metadata:
417 431 if not self.meta:
418 432 raise ValueError("metadata must be specified for table"
419 433 " reflection when using alter_metadata")
420 434 meta = self.meta
421 435 if self.engine:
422 436 meta.bind = self.engine
423 437 else:
424 438 if not self.engine and not self.meta:
425 439 raise ValueError("engine or metadata must be specified"
426 440 " to reflect tables")
427 441 if not self.engine:
428 442 self.engine = self.meta.bind
429 443 meta = sqlalchemy.MetaData(bind=self.engine)
430 444 self._table = sqlalchemy.Table(table, meta, autoload=True)
431 445 elif isinstance(table, sqlalchemy.Table):
432 446 self._table = table
433 447 if not self.alter_metadata:
434 448 self._table.meta = sqlalchemy.MetaData(bind=self._table.bind)
435 449 def _get_result_column(self):
436 450 return getattr(self, '_result_column', None)
437 451
438 452 def _set_result_column(self, column):
439 453 """Set Column to Table based on alter_metadata evaluation."""
440 454 self.process_column(column)
441 455 if not hasattr(self, 'current_name'):
442 456 self.current_name = column.name
443 457 if self.alter_metadata:
444 458 self._result_column = column
445 459 else:
446 460 self._result_column = column.copy_fixed()
447 461
448 462 table = property(_get_table, _set_table)
449 463 result_column = property(_get_result_column, _set_result_column)
450 464
451 465
452 466 class ChangesetTable(object):
453 467 """Changeset extensions to SQLAlchemy tables."""
454 468
455 469 def create_column(self, column, *p, **kw):
456 470 """Creates a column.
457 471
458 472 The column parameter may be a column definition or the name of
459 473 a column in this table.
460 474
461 475 API to :meth:`ChangesetColumn.create`
462 476
463 477 :param column: Column to be created
464 478 :type column: Column instance or string
465 479 """
466 480 if not isinstance(column, sqlalchemy.Column):
467 481 # It's a column name
468 482 column = getattr(self.c, str(column))
469 483 column.create(table=self, *p, **kw)
470 484
471 485 def drop_column(self, column, *p, **kw):
472 486 """Drop a column, given its name or definition.
473 487
474 488 API to :meth:`ChangesetColumn.drop`
475 489
476 490 :param column: Column to be droped
477 491 :type column: Column instance or string
478 492 """
479 493 if not isinstance(column, sqlalchemy.Column):
480 494 # It's a column name
481 495 try:
482 496 column = getattr(self.c, str(column))
483 497 except AttributeError:
484 498 # That column isn't part of the table. We don't need
485 499 # its entire definition to drop the column, just its
486 500 # name, so create a dummy column with the same name.
487 501 column = sqlalchemy.Column(str(column), sqlalchemy.Integer())
488 502 column.drop(table=self, *p, **kw)
489 503
490 504 def rename(self, name, connection=None, **kwargs):
491 505 """Rename this table.
492 506
493 507 :param name: New name of the table.
494 508 :type name: string
495 509 :param connection: reuse connection istead of creating new one.
496 510 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
497 511 """
498 512 engine = self.bind
499 513 self.new_name = name
500 514 visitorcallable = get_engine_visitor(engine, 'schemachanger')
501 515 run_single_visitor(engine, visitorcallable, self, connection, **kwargs)
502 516
503 517 # Fix metadata registration
504 518 self.name = name
505 519 self.deregister()
506 520 self._set_parent(self.metadata)
507 521
508 522 def _meta_key(self):
509 523 """Get the meta key for this table."""
510 524 return sqlalchemy.schema._get_table_key(self.name, self.schema)
511 525
512 526 def deregister(self):
513 527 """Remove this table from its metadata"""
514 528 if SQLA_07:
515 529 self.metadata._remove_table(self.name, self.schema)
516 530 else:
517 531 key = self._meta_key()
518 532 meta = self.metadata
519 533 if key in meta.tables:
520 534 del meta.tables[key]
521 535
522 536
523 537 class ChangesetColumn(object):
524 538 """Changeset extensions to SQLAlchemy columns."""
525 539
526 540 def alter(self, *p, **k):
527 541 """Makes a call to :func:`alter_column` for the column this
528 542 method is called on.
529 543 """
530 544 if 'table' not in k:
531 545 k['table'] = self.table
532 546 if 'engine' not in k:
533 547 k['engine'] = k['table'].bind
534 548 return alter_column(self, *p, **k)
535 549
536 550 def create(self, table=None, index_name=None, unique_name=None,
537 551 primary_key_name=None, populate_default=True, connection=None, **kwargs):
538 552 """Create this column in the database.
539 553
540 554 Assumes the given table exists. ``ALTER TABLE ADD COLUMN``,
541 555 for most databases.
542 556
543 557 :param table: Table instance to create on.
544 558 :param index_name: Creates :class:`ChangesetIndex` on this column.
545 559 :param unique_name: Creates :class:\
546 560 `~migrate.changeset.constraint.UniqueConstraint` on this column.
547 561 :param primary_key_name: Creates :class:\
548 562 `~migrate.changeset.constraint.PrimaryKeyConstraint` on this column.
549 563 :param populate_default: If True, created column will be \
550 564 populated with defaults
551 565 :param connection: reuse connection istead of creating new one.
552 566 :type table: Table instance
553 567 :type index_name: string
554 568 :type unique_name: string
555 569 :type primary_key_name: string
556 570 :type populate_default: bool
557 571 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
558 572
559 573 :returns: self
560 574 """
561 575 self.populate_default = populate_default
562 576 self.index_name = index_name
563 577 self.unique_name = unique_name
564 578 self.primary_key_name = primary_key_name
565 579 for cons in ('index_name', 'unique_name', 'primary_key_name'):
566 580 self._check_sanity_constraints(cons)
567 581
568 582 self.add_to_table(table)
569 583 engine = self.table.bind
570 584 visitorcallable = get_engine_visitor(engine, 'columngenerator')
571 engine._run_visitor(visitorcallable, self, connection, **kwargs)
585 _run_visitor(engine, visitorcallable, self, connection, **kwargs)
572 586
573 587 # TODO: reuse existing connection
574 588 if self.populate_default and self.default is not None:
575 589 stmt = table.update().values({self: engine._execute_default(self.default)})
576 590 engine.execute(stmt)
577 591
578 592 return self
579 593
580 594 def drop(self, table=None, connection=None, **kwargs):
581 595 """Drop this column from the database, leaving its table intact.
582 596
583 597 ``ALTER TABLE DROP COLUMN``, for most databases.
584 598
585 599 :param connection: reuse connection istead of creating new one.
586 600 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
587 601 """
588 602 if table is not None:
589 603 self.table = table
590 604 engine = self.table.bind
591 605 visitorcallable = get_engine_visitor(engine, 'columndropper')
592 engine._run_visitor(visitorcallable, self, connection, **kwargs)
606 _run_visitor(engine, visitorcallable, self, connection, **kwargs)
593 607 self.remove_from_table(self.table, unset_table=False)
594 608 self.table = None
595 609 return self
596 610
597 611 def add_to_table(self, table):
598 612 if table is not None and self.table is None:
599 613 if SQLA_07:
600 614 table.append_column(self)
601 615 else:
602 616 self._set_parent(table)
603 617
604 618 def _col_name_in_constraint(self,cons,name):
605 619 return False
606 620
607 621 def remove_from_table(self, table, unset_table=True):
608 622 # TODO: remove primary keys, constraints, etc
609 623 if unset_table:
610 624 self.table = None
611 625
612 626 to_drop = set()
613 627 for index in table.indexes:
614 628 columns = []
615 629 for col in index.columns:
616 630 if col.name!=self.name:
617 631 columns.append(col)
618 632 if columns:
619 633 index.columns = columns
620 634 if SQLA_08:
621 635 index.expressions = columns
622 636 else:
623 637 to_drop.add(index)
624 638 table.indexes = table.indexes - to_drop
625 639
626 640 to_drop = set()
627 641 for cons in table.constraints:
628 642 # TODO: deal with other types of constraint
629 643 if isinstance(cons,(ForeignKeyConstraint,
630 644 UniqueConstraint)):
631 645 for col_name in cons.columns:
632 646 if not isinstance(col_name, str):
633 647 col_name = col_name.name
634 648 if self.name==col_name:
635 649 to_drop.add(cons)
636 650 table.constraints = table.constraints - to_drop
637 651
638 652 if table.c.contains_column(self):
639 653 if SQLA_07:
640 654 table._columns.remove(self)
641 655 else:
642 656 table.c.remove(self)
643 657
644 658 # TODO: this is fixed in 0.6
645 659 def copy_fixed(self, **kw):
646 660 """Create a copy of this ``Column``, with all attributes."""
647 661 q = util.safe_quote(self)
648 662 return sqlalchemy.Column(self.name, self.type, self.default,
649 663 key=self.key,
650 664 primary_key=self.primary_key,
651 665 nullable=self.nullable,
652 666 quote=q,
653 667 index=self.index,
654 668 unique=self.unique,
655 669 onupdate=self.onupdate,
656 670 autoincrement=self.autoincrement,
657 671 server_default=self.server_default,
658 672 server_onupdate=self.server_onupdate,
659 673 *[c.copy(**kw) for c in self.constraints])
660 674
661 675 def _check_sanity_constraints(self, name):
662 676 """Check if constraints names are correct"""
663 677 obj = getattr(self, name)
664 678 if (getattr(self, name[:-5]) and not obj):
665 679 raise InvalidConstraintError("Column.create() accepts index_name,"
666 680 " primary_key_name and unique_name to generate constraints")
667 681 if not isinstance(obj, str) and obj is not None:
668 682 raise InvalidConstraintError(
669 683 "%s argument for column must be constraint name" % name)
670 684
671 685
672 686 class ChangesetIndex(object):
673 687 """Changeset extensions to SQLAlchemy Indexes."""
674 688
675 689 __visit_name__ = 'index'
676 690
677 691 def rename(self, name, connection=None, **kwargs):
678 692 """Change the name of an index.
679 693
680 694 :param name: New name of the Index.
681 695 :type name: string
682 696 :param connection: reuse connection istead of creating new one.
683 697 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
684 698 """
685 699 engine = self.table.bind
686 700 self.new_name = name
687 701 visitorcallable = get_engine_visitor(engine, 'schemachanger')
688 702 engine._run_visitor(visitorcallable, self, connection, **kwargs)
689 703 self.name = name
690 704
691 705
692 706 class ChangesetDefaultClause(object):
693 707 """Implements comparison between :class:`DefaultClause` instances"""
694 708
695 709 def __eq__(self, other):
696 710 if isinstance(other, self.__class__):
697 711 if self.arg == other.arg:
698 712 return True
699 713
700 714 def __ne__(self, other):
701 715 return not self.__eq__(other)
General Comments 0
You need to be logged in to leave comments. Login now