PageRenderTime 123ms CodeModel.GetById 23ms app.highlight 87ms RepoModel.GetById 1ms app.codeStats 0ms

/SQLAlchemy-0.7.8/test/orm/test_unitofwork.py

#
Python | 2520 lines | 2354 code | 146 blank | 20 comment | 3 complexity | acc97907fe2520ca34bc167f620b437f MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1# coding: utf-8
   2"""Tests unitofwork operations."""
   3
   4from test.lib.testing import eq_, assert_raises, assert_raises_message
   5import datetime
   6import operator
   7from sqlalchemy.orm import mapper as orm_mapper
   8
   9import sqlalchemy as sa
  10from sqlalchemy import Integer, String, ForeignKey, literal_column, event
  11from test.lib import engines, testing, pickleable
  12from test.lib.schema import Table
  13from test.lib.schema import Column
  14from sqlalchemy.orm import mapper, relationship, create_session, \
  15    column_property, attributes, Session, reconstructor, object_session
  16from test.lib.testing import eq_, ne_
  17from test.lib.util import gc_collect
  18from test.lib import fixtures
  19from test.orm import _fixtures
  20from test.lib import fixtures
  21from test.lib.assertsql import AllOf, CompiledSQL
  22import gc
  23
  24class UnitOfWorkTest(object):
  25    pass
  26
  27class HistoryTest(_fixtures.FixtureTest):
  28    run_inserts = None
  29
  30    @classmethod
  31    def setup_classes(cls):
  32        class User(cls.Comparable):
  33            pass
  34        class Address(cls.Comparable):
  35            pass
  36
  37    def test_backref(self):
  38        Address, addresses, users, User = (self.classes.Address,
  39                                self.tables.addresses,
  40                                self.tables.users,
  41                                self.classes.User)
  42
  43        am = mapper(Address, addresses)
  44        m = mapper(User, users, properties=dict(
  45            addresses = relationship(am, backref='user', lazy='joined')))
  46
  47        session = create_session(autocommit=False)
  48
  49        u = User(name='u1')
  50        a = Address(email_address='u1@e')
  51        a.user = u
  52        session.add(u)
  53
  54        eq_(u.addresses, [a])
  55        session.commit()
  56        session.expunge_all()
  57
  58        u = session.query(m).one()
  59        assert u.addresses[0].user == u
  60        session.close()
  61
  62class UnicodeTest(fixtures.MappedTest):
  63    __requires__ = ('unicode_connections',)
  64
  65    @classmethod
  66    def define_tables(cls, metadata):
  67        if testing.against('mysql+oursql'):
  68            from sqlalchemy.dialects.mysql import VARCHAR
  69            uni_type = VARCHAR(50, collation='utf8_unicode_ci')
  70        else:
  71            uni_type = sa.Unicode(50)
  72
  73        Table('uni_t1', metadata,
  74            Column('id',  Integer, primary_key=True,
  75                   test_needs_autoincrement=True),
  76            Column('txt', uni_type, unique=True))
  77        Table('uni_t2', metadata,
  78            Column('id',  Integer, primary_key=True,
  79                   test_needs_autoincrement=True),
  80            Column('txt', uni_type, ForeignKey('uni_t1')))
  81
  82    @classmethod
  83    def setup_classes(cls):
  84        class Test(cls.Basic):
  85            pass
  86        class Test2(cls.Basic):
  87            pass
  88
  89    def test_basic(self):
  90        Test, uni_t1 = self.classes.Test, self.tables.uni_t1
  91
  92        mapper(Test, uni_t1)
  93
  94        txt = u"\u0160\u0110\u0106\u010c\u017d"
  95        t1 = Test(id=1, txt=txt)
  96        self.assert_(t1.txt == txt)
  97
  98        session = create_session(autocommit=False)
  99        session.add(t1)
 100        session.commit()
 101
 102        self.assert_(t1.txt == txt)
 103
 104    def test_relationship(self):
 105        Test, uni_t2, uni_t1, Test2 = (self.classes.Test,
 106                                self.tables.uni_t2,
 107                                self.tables.uni_t1,
 108                                self.classes.Test2)
 109
 110        mapper(Test, uni_t1, properties={
 111            't2s': relationship(Test2)})
 112        mapper(Test2, uni_t2)
 113
 114        txt = u"\u0160\u0110\u0106\u010c\u017d"
 115        t1 = Test(txt=txt)
 116        t1.t2s.append(Test2())
 117        t1.t2s.append(Test2())
 118        session = create_session(autocommit=False)
 119        session.add(t1)
 120        session.commit()
 121        session.close()
 122
 123        session = create_session()
 124        t1 = session.query(Test).filter_by(id=t1.id).one()
 125        assert len(t1.t2s) == 2
 126
 127class UnicodeSchemaTest(fixtures.MappedTest):
 128    __requires__ = ('unicode_connections', 'unicode_ddl',)
 129
 130    run_dispose_bind = 'once'
 131
 132    @classmethod
 133    def create_engine(cls):
 134        return engines.utf8_engine()
 135
 136    @classmethod
 137    def define_tables(cls, metadata):
 138        t1 = Table('unitable1', metadata,
 139              Column(u'méil', Integer, primary_key=True, key='a', test_needs_autoincrement=True),
 140              Column(u'\u6e2c\u8a66', Integer, key='b'),
 141              Column('type',  String(20)),
 142              test_needs_fk=True,
 143              test_needs_autoincrement=True)
 144        t2 = Table(u'Unitéble2', metadata,
 145              Column(u'méil', Integer, primary_key=True, key="cc", test_needs_autoincrement=True),
 146              Column(u'\u6e2c\u8a66', Integer,
 147                     ForeignKey(u'unitable1.a'), key="d"),
 148              Column(u'\u6e2c\u8a66_2', Integer, key="e"),
 149              test_needs_fk=True,
 150              test_needs_autoincrement=True)
 151
 152        cls.tables['t1'] = t1
 153        cls.tables['t2'] = t2
 154
 155    @classmethod
 156    def setup_class(cls):
 157        super(UnicodeSchemaTest, cls).setup_class()
 158
 159    @classmethod
 160    def teardown_class(cls):
 161        super(UnicodeSchemaTest, cls).teardown_class()
 162
 163    @testing.fails_on('mssql+pyodbc',
 164                      'pyodbc returns a non unicode encoding of the results description.')
 165    def test_mapping(self):
 166        t2, t1 = self.tables.t2, self.tables.t1
 167
 168        class A(fixtures.ComparableEntity):
 169            pass
 170        class B(fixtures.ComparableEntity):
 171            pass
 172
 173        mapper(A, t1, properties={
 174            't2s':relationship(B)})
 175        mapper(B, t2)
 176
 177        a1 = A()
 178        b1 = B()
 179        a1.t2s.append(b1)
 180
 181        session = create_session()
 182        session.add(a1)
 183        session.flush()
 184        session.expunge_all()
 185
 186        new_a1 = session.query(A).filter(t1.c.a == a1.a).one()
 187        assert new_a1.a == a1.a
 188        assert new_a1.t2s[0].d == b1.d
 189        session.expunge_all()
 190
 191        new_a1 = (session.query(A).options(sa.orm.joinedload('t2s')).
 192                  filter(t1.c.a == a1.a)).one()
 193        assert new_a1.a == a1.a
 194        assert new_a1.t2s[0].d == b1.d
 195        session.expunge_all()
 196
 197        new_a1 = session.query(A).filter(A.a == a1.a).one()
 198        assert new_a1.a == a1.a
 199        assert new_a1.t2s[0].d == b1.d
 200        session.expunge_all()
 201
 202    @testing.fails_on('mssql+pyodbc',
 203                      'pyodbc returns a non unicode encoding of the results description.')
 204    def test_inheritance_mapping(self):
 205        t2, t1 = self.tables.t2, self.tables.t1
 206
 207        class A(fixtures.ComparableEntity):
 208            pass
 209        class B(A):
 210            pass
 211
 212        mapper(A, t1,
 213               polymorphic_on=t1.c.type,
 214               polymorphic_identity='a')
 215        mapper(B, t2,
 216               inherits=A,
 217               polymorphic_identity='b')
 218        a1 = A(b=5)
 219        b1 = B(e=7)
 220
 221        session = create_session()
 222        session.add_all((a1, b1))
 223        session.flush()
 224        session.expunge_all()
 225
 226        eq_([A(b=5), B(e=7)], session.query(A).all())
 227
 228class BinaryHistTest(fixtures.MappedTest, testing.AssertsExecutionResults):
 229    @classmethod
 230    def define_tables(cls, metadata):
 231        Table('t1', metadata,
 232            Column('id', sa.Integer, primary_key=True, test_needs_autoincrement=True),
 233            Column('data', sa.LargeBinary),
 234        )
 235
 236    @classmethod
 237    def setup_classes(cls):
 238        class Foo(cls.Basic):
 239            pass
 240
 241    def test_binary_equality(self):
 242        Foo, t1 = self.classes.Foo, self.tables.t1
 243
 244
 245        # Py3K
 246        #data = b"this is some data"
 247        # Py2K
 248        data = "this is some data"
 249        # end Py2K
 250
 251        mapper(Foo, t1)
 252
 253        s = create_session()
 254
 255        f1 = Foo(data=data)
 256        s.add(f1)
 257        s.flush()
 258        s.expire_all()
 259        f1 = s.query(Foo).first()
 260        assert f1.data == data
 261        f1.data = data
 262        eq_(
 263            sa.orm.attributes.get_history(f1, "data"),
 264            ((), [data], ())
 265        )
 266        def go():
 267            s.flush()
 268        self.assert_sql_count(testing.db, go, 0)
 269
 270class PKTest(fixtures.MappedTest):
 271
 272    @classmethod
 273    def define_tables(cls, metadata):
 274        Table('multipk1', metadata,
 275              Column('multi_id', Integer, primary_key=True,
 276                     test_needs_autoincrement=True),
 277              Column('multi_rev', Integer, primary_key=True),
 278              Column('name', String(50), nullable=False),
 279              Column('value', String(100)))
 280
 281        Table('multipk2', metadata,
 282              Column('pk_col_1', String(30), primary_key=True),
 283              Column('pk_col_2', String(30), primary_key=True),
 284              Column('data', String(30)))
 285        Table('multipk3', metadata,
 286              Column('pri_code', String(30), key='primary', primary_key=True),
 287              Column('sec_code', String(30), key='secondary', primary_key=True),
 288              Column('date_assigned', sa.Date, key='assigned', primary_key=True),
 289              Column('data', String(30)))
 290
 291    @classmethod
 292    def setup_classes(cls):
 293        class Entry(cls.Basic):
 294            pass
 295
 296    # not supported on sqlite since sqlite's auto-pk generation only works with
 297    # single column primary keys
 298    @testing.fails_on('sqlite', 'FIXME: unknown')
 299    def test_primary_key(self):
 300        Entry, multipk1 = self.classes.Entry, self.tables.multipk1
 301
 302        mapper(Entry, multipk1)
 303
 304        e = Entry(name='entry1', value='this is entry 1', multi_rev=2)
 305
 306        session = create_session()
 307        session.add(e)
 308        session.flush()
 309        session.expunge_all()
 310
 311        e2 = session.query(Entry).get((e.multi_id, 2))
 312        self.assert_(e is not e2)
 313        state = sa.orm.attributes.instance_state(e)
 314        state2 = sa.orm.attributes.instance_state(e2)
 315        eq_(state.key, state2.key)
 316
 317    # this one works with sqlite since we are manually setting up pk values
 318    def test_manual_pk(self):
 319        Entry, multipk2 = self.classes.Entry, self.tables.multipk2
 320
 321        mapper(Entry, multipk2)
 322
 323        e = Entry(pk_col_1='pk1', pk_col_2='pk1_related', data='im the data')
 324
 325        session = create_session()
 326        session.add(e)
 327        session.flush()
 328
 329    def test_key_pks(self):
 330        Entry, multipk3 = self.classes.Entry, self.tables.multipk3
 331
 332        mapper(Entry, multipk3)
 333
 334        e = Entry(primary= 'pk1', secondary='pk2',
 335                   assigned=datetime.date.today(), data='some more data')
 336
 337        session = create_session()
 338        session.add(e)
 339        session.flush()
 340
 341
 342class ForeignPKTest(fixtures.MappedTest):
 343    """Detection of the relationship direction on PK joins."""
 344
 345    @classmethod
 346    def define_tables(cls, metadata):
 347        Table("people", metadata,
 348              Column('person', String(10), primary_key=True),
 349              Column('firstname', String(10)),
 350              Column('lastname', String(10)))
 351
 352        Table("peoplesites", metadata,
 353              Column('person', String(10), ForeignKey("people.person"),
 354                     primary_key=True),
 355              Column('site', String(10)))
 356
 357    @classmethod
 358    def setup_classes(cls):
 359        class Person(cls.Basic):
 360            pass
 361        class PersonSite(cls.Basic):
 362            pass
 363
 364    def test_basic(self):
 365        peoplesites, PersonSite, Person, people = (self.tables.peoplesites,
 366                                self.classes.PersonSite,
 367                                self.classes.Person,
 368                                self.tables.people)
 369
 370        m1 = mapper(PersonSite, peoplesites)
 371        m2 = mapper(Person, people, properties={
 372            'sites' : relationship(PersonSite)})
 373
 374        sa.orm.configure_mappers()
 375        eq_(list(m2.get_property('sites').synchronize_pairs),
 376            [(people.c.person, peoplesites.c.person)])
 377
 378        p = Person(person='im the key', firstname='asdf')
 379        ps = PersonSite(site='asdf')
 380        p.sites.append(ps)
 381
 382        session = create_session()
 383        session.add(p)
 384        session.flush()
 385
 386        p_count = people.count(people.c.person=='im the key').scalar()
 387        eq_(p_count, 1)
 388        eq_(peoplesites.count(peoplesites.c.person=='im the key').scalar(), 1)
 389
 390
 391class ClauseAttributesTest(fixtures.MappedTest):
 392
 393    @classmethod
 394    def define_tables(cls, metadata):
 395        Table('users_t', metadata,
 396            Column('id', Integer, primary_key=True,
 397                   test_needs_autoincrement=True),
 398            Column('name', String(30)),
 399            Column('counter', Integer, default=1))
 400
 401    @classmethod
 402    def setup_classes(cls):
 403        class User(cls.Comparable):
 404            pass
 405
 406    @classmethod
 407    def setup_mappers(cls):
 408        User, users_t = cls.classes.User, cls.tables.users_t
 409
 410        mapper(User, users_t)
 411
 412    def test_update(self):
 413        User = self.classes.User
 414
 415        u = User(name='test')
 416
 417        session = create_session()
 418        session.add(u)
 419        session.flush()
 420
 421        eq_(u.counter, 1)
 422        u.counter = User.counter + 1
 423        session.flush()
 424
 425        def go():
 426            assert (u.counter == 2) is True  # ensure its not a ClauseElement
 427        self.sql_count_(1, go)
 428
 429    def test_multi_update(self):
 430        User = self.classes.User
 431
 432        u = User(name='test')
 433
 434        session = create_session()
 435        session.add(u)
 436        session.flush()
 437
 438        eq_(u.counter, 1)
 439        u.name = 'test2'
 440        u.counter = User.counter + 1
 441        session.flush()
 442
 443        def go():
 444            eq_(u.name, 'test2')
 445            assert (u.counter == 2) is True
 446        self.sql_count_(1, go)
 447
 448        session.expunge_all()
 449        u = session.query(User).get(u.id)
 450        eq_(u.name, 'test2')
 451        eq_(u.counter,  2)
 452
 453    def test_insert(self):
 454        User = self.classes.User
 455
 456        u = User(name='test', counter=sa.select([5]))
 457
 458        session = create_session()
 459        session.add(u)
 460        session.flush()
 461
 462        assert (u.counter == 5) is True
 463
 464
 465class PassiveDeletesTest(fixtures.MappedTest):
 466    __requires__ = ('foreign_keys',)
 467
 468    @classmethod
 469    def define_tables(cls, metadata):
 470        Table('mytable', metadata,
 471              Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 472              Column('data', String(30)),
 473              test_needs_fk=True)
 474
 475        Table('myothertable', metadata,
 476              Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 477              Column('parent_id', Integer),
 478              Column('data', String(30)),
 479              sa.ForeignKeyConstraint(['parent_id'],
 480                                      ['mytable.id'],
 481                                      ondelete="CASCADE"),
 482              test_needs_fk=True)
 483
 484    @classmethod
 485    def setup_classes(cls):
 486        class MyClass(cls.Basic):
 487            pass
 488        class MyOtherClass(cls.Basic):
 489            pass
 490
 491    def test_basic(self):
 492        myothertable, MyClass, MyOtherClass, mytable = (self.tables.myothertable,
 493                                self.classes.MyClass,
 494                                self.classes.MyOtherClass,
 495                                self.tables.mytable)
 496
 497        mapper(MyOtherClass, myothertable)
 498        mapper(MyClass, mytable, properties={
 499            'children':relationship(MyOtherClass,
 500                                passive_deletes=True,
 501                                cascade="all")})
 502        session = create_session()
 503        mc = MyClass()
 504        mc.children.append(MyOtherClass())
 505        mc.children.append(MyOtherClass())
 506        mc.children.append(MyOtherClass())
 507        mc.children.append(MyOtherClass())
 508
 509        session.add(mc)
 510        session.flush()
 511        session.expunge_all()
 512
 513        assert myothertable.count().scalar() == 4
 514        mc = session.query(MyClass).get(mc.id)
 515        session.delete(mc)
 516        session.flush()
 517
 518        assert mytable.count().scalar() == 0
 519        assert myothertable.count().scalar() == 0
 520
 521    @testing.emits_warning(r".*'passive_deletes' is normally configured on one-to-many")
 522    def test_backwards_pd(self):
 523        """Test that passive_deletes=True disables a delete from an m2o.
 524
 525        This is not the usual usage and it now raises a warning, but test
 526        that it works nonetheless.
 527
 528        """
 529
 530        myothertable, MyClass, MyOtherClass, mytable = (self.tables.myothertable,
 531                                self.classes.MyClass,
 532                                self.classes.MyOtherClass,
 533                                self.tables.mytable)
 534
 535        mapper(MyOtherClass, myothertable, properties={
 536            'myclass':relationship(MyClass, cascade="all, delete", passive_deletes=True)
 537        })
 538        mapper(MyClass, mytable)
 539
 540        session = create_session()
 541        mc = MyClass()
 542        mco = MyOtherClass()
 543        mco.myclass = mc
 544        session.add(mco)
 545        session.flush()
 546
 547        assert mytable.count().scalar() == 1
 548        assert myothertable.count().scalar() == 1
 549
 550        session.expire(mco, ['myclass'])
 551        session.delete(mco)
 552        session.flush()
 553
 554        # mytable wasn't deleted, is the point.
 555        assert mytable.count().scalar() == 1
 556        assert myothertable.count().scalar() == 0
 557
 558    def test_aaa_m2o_emits_warning(self):
 559        myothertable, MyClass, MyOtherClass, mytable = (self.tables.myothertable,
 560                                self.classes.MyClass,
 561                                self.classes.MyOtherClass,
 562                                self.tables.mytable)
 563
 564        mapper(MyOtherClass, myothertable, properties={
 565            'myclass':relationship(MyClass, cascade="all, delete", passive_deletes=True)
 566        })
 567        mapper(MyClass, mytable)
 568        assert_raises(sa.exc.SAWarning, sa.orm.configure_mappers)
 569
 570class BatchDeleteIgnoresRowcountTest(fixtures.DeclarativeMappedTest):
 571    __requires__ = ('foreign_keys',)
 572    @classmethod
 573    def setup_classes(cls):
 574        class A(cls.DeclarativeBasic):
 575            __tablename__ = 'A'
 576            __table_args__ = dict(test_needs_fk=True)
 577            id = Column(Integer, primary_key=True)
 578            parent_id = Column(Integer, ForeignKey('A.id', ondelete='CASCADE'))
 579
 580    def test_delete_both(self):
 581        A = self.classes.A
 582        session = Session(testing.db)
 583
 584        a1, a2 = A(id=1),A(id=2, parent_id=1)
 585
 586        session.add_all([a1, a2])
 587        session.flush()
 588
 589        session.delete(a1)
 590        session.delete(a2)
 591
 592        # no issue with multi-row count here
 593        session.flush()
 594
 595class ExtraPassiveDeletesTest(fixtures.MappedTest):
 596    __requires__ = ('foreign_keys',)
 597
 598    @classmethod
 599    def define_tables(cls, metadata):
 600        Table('mytable', metadata,
 601              Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 602              Column('data', String(30)),
 603              test_needs_fk=True)
 604
 605        Table('myothertable', metadata,
 606              Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 607              Column('parent_id', Integer),
 608              Column('data', String(30)),
 609              # no CASCADE, the same as ON DELETE RESTRICT
 610              sa.ForeignKeyConstraint(['parent_id'],
 611                                      ['mytable.id']),
 612              test_needs_fk=True)
 613
 614    @classmethod
 615    def setup_classes(cls):
 616        class MyClass(cls.Basic):
 617            pass
 618        class MyOtherClass(cls.Basic):
 619            pass
 620
 621    def test_assertions(self):
 622        myothertable, MyOtherClass = self.tables.myothertable, self.classes.MyOtherClass
 623
 624        mapper(MyOtherClass, myothertable)
 625        assert_raises_message(
 626            sa.exc.ArgumentError,
 627            "Can't set passive_deletes='all' in conjunction with 'delete' "
 628            "or 'delete-orphan' cascade",
 629            relationship, MyOtherClass,
 630                                    passive_deletes='all',
 631                                    cascade="all"
 632        )
 633
 634    def test_extra_passive(self):
 635        myothertable, MyClass, MyOtherClass, mytable = (self.tables.myothertable,
 636                                self.classes.MyClass,
 637                                self.classes.MyOtherClass,
 638                                self.tables.mytable)
 639
 640        mapper(MyOtherClass, myothertable)
 641        mapper(MyClass, mytable, properties={
 642            'children': relationship(MyOtherClass,
 643                                 passive_deletes='all',
 644                                 cascade="save-update")})
 645
 646        session = create_session()
 647        mc = MyClass()
 648        mc.children.append(MyOtherClass())
 649        mc.children.append(MyOtherClass())
 650        mc.children.append(MyOtherClass())
 651        mc.children.append(MyOtherClass())
 652        session.add(mc)
 653        session.flush()
 654        session.expunge_all()
 655
 656        assert myothertable.count().scalar() == 4
 657        mc = session.query(MyClass).get(mc.id)
 658        session.delete(mc)
 659        assert_raises(sa.exc.DBAPIError, session.flush)
 660
 661    def test_extra_passive_2(self):
 662        myothertable, MyClass, MyOtherClass, mytable = (self.tables.myothertable,
 663                                self.classes.MyClass,
 664                                self.classes.MyOtherClass,
 665                                self.tables.mytable)
 666
 667        mapper(MyOtherClass, myothertable)
 668        mapper(MyClass, mytable, properties={
 669            'children': relationship(MyOtherClass,
 670                                 passive_deletes='all',
 671                                 cascade="save-update")})
 672
 673        session = create_session()
 674        mc = MyClass()
 675        mc.children.append(MyOtherClass())
 676        session.add(mc)
 677        session.flush()
 678        session.expunge_all()
 679
 680        assert myothertable.count().scalar() == 1
 681
 682        mc = session.query(MyClass).get(mc.id)
 683        session.delete(mc)
 684        mc.children[0].data = 'some new data'
 685        assert_raises(sa.exc.DBAPIError, session.flush)
 686
 687    def test_dont_emit(self):
 688        myothertable, MyClass, MyOtherClass, mytable = (self.tables.myothertable,
 689                                self.classes.MyClass,
 690                                self.classes.MyOtherClass,
 691                                self.tables.mytable)
 692
 693        mapper(MyOtherClass, myothertable)
 694        mapper(MyClass, mytable, properties={
 695            'children': relationship(MyOtherClass,
 696                                 passive_deletes='all',
 697                                 cascade="save-update")})
 698        session = Session()
 699        mc = MyClass()
 700        session.add(mc)
 701        session.commit()
 702        mc.id
 703
 704        session.delete(mc)
 705
 706        # no load for "children" should occur
 707        self.assert_sql_count(testing.db, session.flush, 1)
 708
 709class ColumnCollisionTest(fixtures.MappedTest):
 710    """Ensure the mapper doesn't break bind param naming rules on flush."""
 711
 712    @classmethod
 713    def define_tables(cls, metadata):
 714        Table('book', metadata,
 715            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 716            Column('book_id', String(50)),
 717            Column('title', String(50))
 718        )
 719
 720    def test_naming(self):
 721        book = self.tables.book
 722
 723        class Book(fixtures.ComparableEntity):
 724            pass
 725
 726        mapper(Book, book)
 727        sess = create_session()
 728
 729        b1 = Book(book_id='abc', title='def')
 730        sess.add(b1)
 731        sess.flush()
 732
 733        b1.title = 'ghi'
 734        sess.flush()
 735        sess.close()
 736        eq_(
 737            sess.query(Book).first(),
 738            Book(book_id='abc', title='ghi')
 739        )
 740
 741
 742
 743class DefaultTest(fixtures.MappedTest):
 744    """Exercise mappings on columns with DefaultGenerators.
 745
 746    Tests that when saving objects whose table contains DefaultGenerators,
 747    either python-side, preexec or database-side, the newly saved instances
 748    receive all the default values either through a post-fetch or getting the
 749    pre-exec'ed defaults back from the engine.
 750
 751    """
 752
 753    @classmethod
 754    def define_tables(cls, metadata):
 755        use_string_defaults = testing.against('postgresql', 'oracle', 'sqlite', 'mssql')
 756
 757        if use_string_defaults:
 758            hohotype = String(30)
 759            hohoval = "im hoho"
 760            althohoval = "im different hoho"
 761        else:
 762            hohotype = Integer
 763            hohoval = 9
 764            althohoval = 15
 765
 766        cls.other['hohoval'] = hohoval
 767        cls.other['althohoval'] = althohoval
 768
 769        dt = Table('default_t', metadata,
 770            Column('id', Integer, primary_key=True,
 771                   test_needs_autoincrement=True),
 772            Column('hoho', hohotype, server_default=str(hohoval)),
 773            Column('counter', Integer, default=sa.func.char_length("1234567", type_=Integer)),
 774            Column('foober', String(30), default="im foober", onupdate="im the update"),
 775            mysql_engine='MyISAM')
 776
 777        st = Table('secondary_table', metadata,
 778            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 779            Column('data', String(50)),
 780            mysql_engine='MyISAM')
 781
 782        if testing.against('postgresql', 'oracle'):
 783            dt.append_column(
 784                Column('secondary_id', Integer, sa.Sequence('sec_id_seq'),
 785                       unique=True))
 786            st.append_column(
 787                Column('fk_val', Integer,
 788                       ForeignKey('default_t.secondary_id')))
 789        elif testing.against('mssql'):
 790            st.append_column(
 791                Column('fk_val', Integer,
 792                       ForeignKey('default_t.id')))
 793        else:
 794            st.append_column(
 795                Column('hoho', hohotype, ForeignKey('default_t.hoho')))
 796
 797    @classmethod
 798    def setup_classes(cls):
 799        class Hoho(cls.Comparable):
 800            pass
 801        class Secondary(cls.Comparable):
 802            pass
 803
 804    @testing.fails_on('firebird', 'Data type unknown on the parameter')
 805    def test_insert(self):
 806        althohoval, hohoval, default_t, Hoho = (self.other.althohoval,
 807                                self.other.hohoval,
 808                                self.tables.default_t,
 809                                self.classes.Hoho)
 810
 811        mapper(Hoho, default_t)
 812
 813        h1 = Hoho(hoho=althohoval)
 814        h2 = Hoho(counter=12)
 815        h3 = Hoho(hoho=althohoval, counter=12)
 816        h4 = Hoho()
 817        h5 = Hoho(foober='im the new foober')
 818
 819        session = create_session(autocommit=False)
 820        session.add_all((h1, h2, h3, h4, h5))
 821        session.commit()
 822
 823        eq_(h1.hoho, althohoval)
 824        eq_(h3.hoho, althohoval)
 825
 826        def go():
 827            # test deferred load of attribues, one select per instance
 828            self.assert_(h2.hoho == h4.hoho == h5.hoho == hohoval)
 829        self.sql_count_(3, go)
 830
 831        def go():
 832            self.assert_(h1.counter == h4.counter == h5.counter == 7)
 833        self.sql_count_(1, go)
 834
 835        def go():
 836            self.assert_(h3.counter == h2.counter == 12)
 837            self.assert_(h2.foober == h3.foober == h4.foober == 'im foober')
 838            self.assert_(h5.foober == 'im the new foober')
 839        self.sql_count_(0, go)
 840
 841        session.expunge_all()
 842
 843        (h1, h2, h3, h4, h5) = session.query(Hoho).order_by(Hoho.id).all()
 844
 845        eq_(h1.hoho, althohoval)
 846        eq_(h3.hoho, althohoval)
 847        self.assert_(h2.hoho == h4.hoho == h5.hoho == hohoval)
 848        self.assert_(h3.counter == h2.counter == 12)
 849        self.assert_(h1.counter ==  h4.counter == h5.counter == 7)
 850        self.assert_(h2.foober == h3.foober == h4.foober == 'im foober')
 851        eq_(h5.foober, 'im the new foober')
 852
 853    @testing.fails_on('firebird', 'Data type unknown on the parameter')
 854    def test_eager_defaults(self):
 855        hohoval, default_t, Hoho = (self.other.hohoval,
 856                                self.tables.default_t,
 857                                self.classes.Hoho)
 858
 859        mapper(Hoho, default_t, eager_defaults=True)
 860
 861        h1 = Hoho()
 862
 863        session = create_session()
 864        session.add(h1)
 865        session.flush()
 866
 867        self.sql_count_(0, lambda: eq_(h1.hoho, hohoval))
 868
 869    def test_insert_nopostfetch(self):
 870        default_t, Hoho = self.tables.default_t, self.classes.Hoho
 871
 872        # populates from the FetchValues explicitly so there is no
 873        # "post-update"
 874        mapper(Hoho, default_t)
 875
 876        h1 = Hoho(hoho="15", counter=15)
 877        session = create_session()
 878        session.add(h1)
 879        session.flush()
 880
 881        def go():
 882            eq_(h1.hoho, "15")
 883            eq_(h1.counter, 15)
 884            eq_(h1.foober, "im foober")
 885        self.sql_count_(0, go)
 886
 887    @testing.fails_on('firebird', 'Data type unknown on the parameter')
 888    def test_update(self):
 889        default_t, Hoho = self.tables.default_t, self.classes.Hoho
 890
 891        mapper(Hoho, default_t)
 892
 893        h1 = Hoho()
 894        session = create_session()
 895        session.add(h1)
 896        session.flush()
 897
 898        eq_(h1.foober, 'im foober')
 899        h1.counter = 19
 900        session.flush()
 901        eq_(h1.foober, 'im the update')
 902
 903    @testing.fails_on('firebird', 'Data type unknown on the parameter')
 904    def test_used_in_relationship(self):
 905        """A server-side default can be used as the target of a foreign key"""
 906
 907        Hoho, hohoval, default_t, secondary_table, Secondary = (self.classes.Hoho,
 908                                self.other.hohoval,
 909                                self.tables.default_t,
 910                                self.tables.secondary_table,
 911                                self.classes.Secondary)
 912
 913
 914        mapper(Hoho, default_t, properties={
 915            'secondaries':relationship(Secondary, order_by=secondary_table.c.id)})
 916        mapper(Secondary, secondary_table)
 917
 918        h1 = Hoho()
 919        s1 = Secondary(data='s1')
 920        h1.secondaries.append(s1)
 921
 922        session = create_session()
 923        session.add(h1)
 924        session.flush()
 925        session.expunge_all()
 926
 927        eq_(session.query(Hoho).get(h1.id),
 928            Hoho(hoho=hohoval,
 929                 secondaries=[
 930                   Secondary(data='s1')]))
 931
 932        h1 = session.query(Hoho).get(h1.id)
 933        h1.secondaries.append(Secondary(data='s2'))
 934        session.flush()
 935        session.expunge_all()
 936
 937        eq_(session.query(Hoho).get(h1.id),
 938            Hoho(hoho=hohoval,
 939                 secondaries=[
 940                    Secondary(data='s1'),
 941                    Secondary(data='s2')]))
 942
 943class ColumnPropertyTest(fixtures.MappedTest):
 944    @classmethod
 945    def define_tables(cls, metadata):
 946        Table('data', metadata, 
 947            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 948            Column('a', String(50)),
 949            Column('b', String(50))
 950            )
 951
 952        Table('subdata', metadata, 
 953            Column('id', Integer, ForeignKey('data.id'), primary_key=True),
 954            Column('c', String(50)),
 955            )
 956
 957    @classmethod
 958    def setup_mappers(cls):
 959        class Data(cls.Basic):
 960            pass
 961
 962    def test_refreshes(self):
 963        Data, data = self.classes.Data, self.tables.data
 964
 965        mapper(Data, data, properties={
 966            'aplusb':column_property(data.c.a + literal_column("' '") + data.c.b)
 967        })
 968        self._test(True)
 969
 970    def test_no_refresh(self):
 971        Data, data = self.classes.Data, self.tables.data
 972
 973        mapper(Data, data, properties={
 974            'aplusb':column_property(data.c.a + literal_column("' '") + data.c.b, 
 975                        expire_on_flush=False)
 976        })
 977        self._test(False)
 978
 979    def test_refreshes_post_init(self):
 980        Data, data = self.classes.Data, self.tables.data
 981
 982        m = mapper(Data, data)
 983        m.add_property('aplusb', column_property(data.c.a + literal_column("' '") + data.c.b))
 984        self._test(True)
 985
 986    def test_with_inheritance(self):
 987        subdata, data, Data = (self.tables.subdata,
 988                                self.tables.data,
 989                                self.classes.Data)
 990
 991        class SubData(Data):
 992            pass
 993        mapper(Data, data, properties={
 994            'aplusb':column_property(data.c.a + literal_column("' '") + data.c.b)
 995        })
 996        mapper(SubData, subdata, inherits=Data)
 997
 998        sess = create_session()
 999        sd1 = SubData(a="hello", b="there", c="hi")
1000        sess.add(sd1)
1001        sess.flush()
1002        eq_(sd1.aplusb, "hello there")
1003
1004    def _test(self, expect_expiry):
1005        Data = self.classes.Data
1006
1007        sess = create_session()
1008
1009        d1 = Data(a="hello", b="there")
1010        sess.add(d1)
1011        sess.flush()
1012
1013        eq_(d1.aplusb, "hello there")
1014
1015        d1.b = "bye"
1016        sess.flush()
1017        if expect_expiry:
1018            eq_(d1.aplusb, "hello bye")
1019        else:
1020            eq_(d1.aplusb, "hello there")
1021
1022
1023        d1.b = 'foobar'
1024        d1.aplusb = 'im setting this explicitly'
1025        sess.flush()
1026        eq_(d1.aplusb, "im setting this explicitly")
1027
1028class OneToManyTest(_fixtures.FixtureTest):
1029    run_inserts = None
1030
1031    def test_one_to_many_1(self):
1032        """Basic save of one to many."""
1033
1034        Address, addresses, users, User = (self.classes.Address,
1035                                self.tables.addresses,
1036                                self.tables.users,
1037                                self.classes.User)
1038
1039
1040        m = mapper(User, users, properties=dict(
1041            addresses = relationship(mapper(Address, addresses), lazy='select')
1042        ))
1043        u = User(name= 'one2manytester')
1044        a = Address(email_address='one2many@test.org')
1045        u.addresses.append(a)
1046
1047        a2 = Address(email_address='lala@test.org')
1048        u.addresses.append(a2)
1049
1050        session = create_session()
1051        session.add(u)
1052        session.flush()
1053
1054        user_rows = users.select(users.c.id.in_([u.id])).execute().fetchall()
1055        eq_(user_rows[0].values(), [u.id, 'one2manytester'])
1056
1057        address_rows = addresses.select(
1058            addresses.c.id.in_([a.id, a2.id]),
1059            order_by=[addresses.c.email_address]).execute().fetchall()
1060        eq_(address_rows[0].values(), [a2.id, u.id, 'lala@test.org'])
1061        eq_(address_rows[1].values(), [a.id, u.id, 'one2many@test.org'])
1062
1063        userid = u.id
1064        addressid = a2.id
1065
1066        a2.email_address = 'somethingnew@foo.com'
1067
1068        session.flush()
1069
1070        address_rows = addresses.select(
1071            addresses.c.id == addressid).execute().fetchall()
1072        eq_(address_rows[0].values(),
1073            [addressid, userid, 'somethingnew@foo.com'])
1074        self.assert_(u.id == userid and a2.id == addressid)
1075
1076    def test_one_to_many_2(self):
1077        """Modifying the child items of an object."""
1078
1079        Address, addresses, users, User = (self.classes.Address,
1080                                self.tables.addresses,
1081                                self.tables.users,
1082                                self.classes.User)
1083
1084
1085        m = mapper(User, users, properties=dict(
1086            addresses = relationship(mapper(Address, addresses), lazy='select')))
1087
1088        u1 = User(name='user1')
1089        u1.addresses = []
1090        a1 = Address(email_address='emailaddress1')
1091        u1.addresses.append(a1)
1092
1093        u2 = User(name='user2')
1094        u2.addresses = []
1095        a2 = Address(email_address='emailaddress2')
1096        u2.addresses.append(a2)
1097
1098        a3 = Address(email_address='emailaddress3')
1099
1100        session = create_session()
1101        session.add_all((u1, u2, a3))
1102        session.flush()
1103
1104        # modify user2 directly, append an address to user1.
1105        # upon commit, user2 should be updated, user1 should not
1106        # both address1 and address3 should be updated
1107        u2.name = 'user2modified'
1108        u1.addresses.append(a3)
1109        del u1.addresses[0]
1110
1111        self.assert_sql(testing.db, session.flush, [
1112            ("UPDATE users SET name=:name "
1113             "WHERE users.id = :users_id",
1114             {'users_id': u2.id, 'name': 'user2modified'}),
1115
1116            ("UPDATE addresses SET user_id=:user_id "
1117             "WHERE addresses.id = :addresses_id",
1118             {'user_id': None, 'addresses_id': a1.id}),
1119
1120            ("UPDATE addresses SET user_id=:user_id "
1121             "WHERE addresses.id = :addresses_id",
1122             {'user_id': u1.id, 'addresses_id': a3.id})])
1123
1124    def test_child_move(self):
1125        """Moving a child from one parent to another, with a delete.
1126
1127        Tests that deleting the first parent properly updates the child with
1128        the new parent.  This tests the 'trackparent' option in the attributes
1129        module.
1130
1131        """
1132
1133        Address, addresses, users, User = (self.classes.Address,
1134                                self.tables.addresses,
1135                                self.tables.users,
1136                                self.classes.User)
1137
1138        m = mapper(User, users, properties=dict(
1139            addresses = relationship(mapper(Address, addresses), lazy='select')))
1140
1141        u1 = User(name='user1')
1142        u2 = User(name='user2')
1143        a = Address(email_address='address1')
1144        u1.addresses.append(a)
1145
1146        session = create_session()
1147        session.add_all((u1, u2))
1148        session.flush()
1149
1150        del u1.addresses[0]
1151        u2.addresses.append(a)
1152        session.delete(u1)
1153
1154        session.flush()
1155        session.expunge_all()
1156
1157        u2 = session.query(User).get(u2.id)
1158        eq_(len(u2.addresses), 1)
1159
1160    def test_child_move_2(self):
1161        Address, addresses, users, User = (self.classes.Address,
1162                                self.tables.addresses,
1163                                self.tables.users,
1164                                self.classes.User)
1165
1166        m = mapper(User, users, properties=dict(
1167            addresses = relationship(mapper(Address, addresses), lazy='select')))
1168
1169        u1 = User(name='user1')
1170        u2 = User(name='user2')
1171        a = Address(email_address='address1')
1172        u1.addresses.append(a)
1173
1174        session = create_session()
1175        session.add_all((u1, u2))
1176        session.flush()
1177
1178        del u1.addresses[0]
1179        u2.addresses.append(a)
1180
1181        session.flush()
1182        session.expunge_all()
1183
1184        u2 = session.query(User).get(u2.id)
1185        eq_(len(u2.addresses), 1)
1186
1187    def test_o2m_delete_parent(self):
1188        Address, addresses, users, User = (self.classes.Address,
1189                                self.tables.addresses,
1190                                self.tables.users,
1191                                self.classes.User)
1192
1193        m = mapper(User, users, properties=dict(
1194            address = relationship(mapper(Address, addresses),
1195                               lazy='select',
1196                               uselist=False)))
1197
1198        u = User(name='one2onetester')
1199        a = Address(email_address='myonlyaddress@foo.com')
1200        u.address = a
1201
1202        session = create_session()
1203        session.add(u)
1204        session.flush()
1205
1206        session.delete(u)
1207        session.flush()
1208
1209        assert a.id is not None
1210        assert a.user_id is None
1211        assert sa.orm.attributes.instance_state(a).key in session.identity_map
1212        assert sa.orm.attributes.instance_state(u).key not in session.identity_map
1213
1214    def test_one_to_one(self):
1215        Address, addresses, users, User = (self.classes.Address,
1216                                self.tables.addresses,
1217                                self.tables.users,
1218                                self.classes.User)
1219
1220        m = mapper(User, users, properties=dict(
1221            address = relationship(mapper(Address, addresses),
1222                               lazy='select',
1223                               uselist=False)))
1224
1225        u = User(name='one2onetester')
1226        u.address = Address(email_address='myonlyaddress@foo.com')
1227
1228        session = create_session()
1229        session.add(u)
1230        session.flush()
1231
1232        u.name = 'imnew'
1233        session.flush()
1234
1235        u.address.email_address = 'imnew@foo.com'
1236        session.flush()
1237
1238    def test_bidirectional(self):
1239        users, Address, addresses, User = (self.tables.users,
1240                                self.classes.Address,
1241                                self.tables.addresses,
1242                                self.classes.User)
1243
1244        m1 = mapper(User, users)
1245        m2 = mapper(Address, addresses, properties=dict(
1246            user = relationship(m1, lazy='joined', backref='addresses')))
1247
1248
1249        u = User(name='test')
1250        a = Address(email_address='testaddress', user=u)
1251
1252        session = create_session()
1253        session.add(u)
1254        session.flush()
1255        session.delete(u)
1256        session.flush()
1257
1258    def test_double_relationship(self):
1259        Address, addresses, users, User = (self.classes.Address,
1260                                self.tables.addresses,
1261                                self.tables.users,
1262                                self.classes.User)
1263
1264        m2 = mapper(Address, addresses)
1265        m = mapper(User, users, properties={
1266            'boston_addresses' : relationship(m2, primaryjoin=
1267                        sa.and_(users.c.id==addresses.c.user_id,
1268                                addresses.c.email_address.like('%boston%'))),
1269            'newyork_addresses' : relationship(m2, primaryjoin=
1270                        sa.and_(users.c.id==addresses.c.user_id,
1271                                addresses.c.email_address.like('%newyork%')))})
1272
1273        u = User(name='u1')
1274        a = Address(email_address='foo@boston.com')
1275        b = Address(email_address='bar@newyork.com')
1276        u.boston_addresses.append(a)
1277        u.newyork_addresses.append(b)
1278
1279        session = create_session()
1280        session.add(u)
1281        session.flush()
1282
1283class SaveTest(_fixtures.FixtureTest):
1284    run_inserts = None
1285
1286    def test_basic(self):
1287        User, users = self.classes.User, self.tables.users
1288
1289        m = mapper(User, users)
1290
1291        # save two users
1292        u = User(name='savetester')
1293        u2 = User(name='savetester2')
1294
1295        session = create_session()
1296        session.add_all((u, u2))
1297        session.flush()
1298
1299        # assert the first one retreives the same from the identity map
1300        nu = session.query(m).get(u.id)
1301        assert u is nu
1302
1303        # clear out the identity map, so next get forces a SELECT
1304        session.expunge_all()
1305
1306        # check it again, identity should be different but ids the same
1307        nu = session.query(m).get(u.id)
1308        assert u is not nu and u.id == nu.id and nu.name == 'savetester'
1309
1310        # change first users name and save
1311        session = create_session()
1312        session.add(u)
1313        u.name = 'modifiedname'
1314        assert u in session.dirty
1315        session.flush()
1316
1317        # select both
1318        userlist = session.query(User).filter(
1319            users.c.id.in_([u.id, u2.id])).order_by(users.c.name).all()
1320
1321        eq_(u.id, userlist[0].id)
1322        eq_(userlist[0].name, 'modifiedname')
1323        eq_(u2.id, userlist[1].id)
1324        eq_(userlist[1].name, 'savetester2')
1325
1326    def test_synonym(self):
1327        users = self.tables.users
1328
1329        class SUser(fixtures.BasicEntity):
1330            def _get_name(self):
1331                return "User:" + self.name
1332            def _set_name(self, name):
1333                self.name = name + ":User"
1334            syn_name = property(_get_name, _set_name)
1335
1336        mapper(SUser, users, properties={
1337            'syn_name': sa.orm.synonym('name')
1338        })
1339
1340        u = SUser(syn_name="some name")
1341        eq_(u.syn_name, 'User:some name:User')
1342
1343        session = create_session()
1344        session.add(u)
1345        session.flush()
1346        session.expunge_all()
1347
1348        u = session.query(SUser).first()
1349        eq_(u.syn_name, 'User:some name:User')
1350
1351    def test_lazyattr_commit(self):
1352        """Lazily loaded relationships.
1353
1354        When a lazy-loaded list is unloaded, and a commit occurs, that the
1355        'passive' call on that list does not blow away its value
1356
1357        """
1358
1359        users, Address, addresses, User = (self.tables.users,
1360                                self.classes.Address,
1361                                self.tables.addresses,
1362                                self.classes.User)
1363
1364        mapper(User, users, properties = {
1365            'addresses': relationship(mapper(Address, addresses))})
1366
1367        u = User(name='u1')
1368        u.addresses.append(Address(email_address='u1@e1'))
1369        u.addresses.append(Address(email_address='u1@e2'))
1370        u.addresses.append(Address(email_address='u1@e3'))
1371        u.addresses.append(Address(email_address='u1@e4'))
1372
1373        session = create_session()
1374        session.add(u)
1375        session.flush()
1376        session.expunge_all()
1377
1378        u = session.query(User).one()
1379        u.name = 'newname'
1380        session.flush()
1381        eq_(len(u.addresses), 4)
1382
1383    def test_inherits(self):
1384        """a user object that also has the users mailing address."""
1385
1386        users, addresses, User = (self.tables.users,
1387                                self.tables.addresses,
1388                                self.classes.User)
1389
1390        m1 = mapper(User, users)
1391
1392        class AddressUser(User):
1393            pass
1394
1395        # define a mapper for AddressUser that inherits the User.mapper, and
1396        # joins on the id column
1397        mapper(AddressUser, addresses, inherits=m1)
1398
1399        au = AddressUser(name='u', email_address='u@e')
1400
1401        session = create_session()
1402        session.add(au)
1403        session.flush()
1404        session.expunge_all()
1405
1406        rt = session.query(AddressUser).one()
1407        eq_(au.user_id, rt.user_id)
1408        eq_(rt.id, rt.id)
1409
1410    def test_deferred(self):
1411        """Deferred column operations"""
1412
1413        orders, Order = self.tables.orders, self.classes.Order
1414
1415
1416        mapper(Order, orders, properties={
1417            'description': sa.orm.deferred(orders.c.description)})
1418
1419        # dont set deferred attribute, commit session
1420        o = Order(id=42)
1421        session = create_session(autocommit=False)
1422        session.add(o)
1423        session.commit()
1424
1425        # assert that changes get picked up
1426        o.description = 'foo'
1427        session.commit()
1428
1429        eq_(list(session.execute(orders.select(), mapper=Order)),
1430            [(42, None, None, 'foo', None)])
1431        session.expunge_all()
1432
1433        # assert that a set operation doesn't trigger a load operation
1434        o = session.query(Order).filter(Order.description == 'foo').one()
1435        def go():
1436            o.description = 'hoho'
1437        self.sql_count_(0, go)
1438        session.flush()
1439
1440        eq_(list(session.execute(orders.select(), mapper=Order)),
1441            [(42, None, None, 'hoho', None)])
1442
1443        session.expunge_all()
1444
1445        # test assigning None to an unloaded deferred also works
1446        o = session.query(Order).filter(Order.description == 'hoho').one()
1447        o.description = None
1448        session.flush()
1449        eq_(list(session.execute(orders.select(), mapper=Order)),
1450            [(42, None, None, None, None)])
1451        session.close()
1452
1453    # why no support on oracle ?  because oracle doesn't save
1454    # "blank" strings; it saves a single space character.
1455    @testing.fails_on('oracle', 'FIXME: unknown')
1456    def test_dont_update_blanks(self):
1457        User, users = self.classes.User, self.tables.users
1458
1459        mapper(User, users)
1460
1461        u = User(name='')
1462        session = create_session()
1463        session.add(u)
1464        session.flush()
1465        session.expunge_all()
1466
1467        u = session.query(User).get(u.id)
1468        u.name = ''
1469        self.sql_count_(0, session.flush)
1470
1471    def test_multi_table_selectable(self):
1472        """Mapped selectables that span tables.
1473
1474        Also tests redefinition of the keynames for the column properties.
1475
1476        """
1477
1478        addresses, users, User = (self.tables.addresses,
1479                                self.tables.users,
1480                                self.classes.User)
1481
1482        usersaddresses = sa.join(users, addresses,
1483                                 users.c.id == addresses.c.user_id)
1484
1485        m = mapper(User, usersaddresses,
1486            properties=dict(
1487                email = addresses.c.email_address,
1488                foo_id = [users.c.id, addresses.c.user_id]))
1489
1490        u = User(name='multitester', email='multi@test.org')
1491        session = create_session()
1492        session.add(u)
1493        session.flush()
1494        session.expunge_all()
1495
1496        id = m.primary_key_from_instance(u)
1497
1498        u = session.query(User).get(id)
1499        assert u.name == 'multitester'
1500
1501        user_rows = users.select(users.c.id.in_([u.foo_id])).execute().fetchall()
1502        eq_(user_rows[0].values(), [u.foo_id, 'multitester'])
1503        address_rows = addresses.select(addresses.c.id.in_([u.id])).execute().fetchall()
1504        eq_(address_rows[0].values(), [u.id, u.foo_id, 'multi@test.org'])
1505
1506        u.email = 'lala@hey.com'
1507        u.name = 'imnew'
1508        session.flush()
1509
1510        user_rows = users.select(users.c.id.in_([u.foo_id])).execute().fetchall()
1511        eq_(user_rows[0].values(), [u.foo_id, 'imnew'])
1512        address_rows = addresses.select(addresses.c.id.in_([u.id])).execute().fetchall()
1513        eq_(address_rows[0].values(), [u.id, u.foo_id, 'lala@hey.com'])
1514
1515        session.expunge_all()
1516        u = session.query(User).get(id)
1517        assert u.name == 'imnew'
1518
1519    def test_history_get(self):
1520        """The history lazy-fetches data when it wasn't otherwise loaded."""
1521
1522        users, Address, addresses, User = (self.tables.users,
1523                                self.classes.Address,
1524                                self.tables.addresses,
1525                                self.classes.User)
1526
1527        mapper(User, users, properties={
1528            'addresses':relationship(Address, cascade="all, delete-orphan")})
1529        mapper(Address, addresses)
1530
1531        u = User(name='u1')
1532        u.addresses.append(Address(email_address='u1@e1'))
1533        u.addresses.append(Address(email_address='u1@e2'))
1534        session = create_session()
1535        session.add(u)
1536        session.flush()
1537        session.expunge_all()
1538
1539        u = session.query(User).get(u.id)
1540        session.delete(u)
1541        session.flush()
1542        assert users.count().scalar() == 0
1543        assert addresses.count().scalar() == 0
1544
1545    def test_batch_mode(self):
1546        """The 'batch=False' flag on mapper()"""
1547
1548        users, User = self.tables.users, self.classes.User
1549
1550
1551        names = []
1552        class Events(object):
1553            def before_insert(self, mapper,

Large files files are truncated, but you can click here to view the full file